Windows Forms - 속성 창의 디자인 설정 지원: 문자열 목록 내에서 항목을 선택하는 TypeConverter 제작
아래와 같은 질문이 있군요.
User Control에 string array 속성 추가하는 방법
; https://www.sysnet.pe.kr/3/0/5511
Windows Forms 응용 프로그램은 Visual Studio의 Properties 창과 연동할 수 있는 다양한 디자인-타임 설정들이 존재합니다. 가령, 다음과 같이 컨트롤에 공용 속성을 정의하면,
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsControlLibrary1
{
public partial class UserControl1: UserControl
{
public UserControl1()
{
InitializeComponent();
}
AccountType _userType;
[Category("MyUserControlType")]
public AccountType UserType
{
get { return _userType; }
set { _userType = value; }
}
}
public enum AccountType
{
A,
B,
C
}
}
다음과 같은 식으로 디자인 창에서 해당 컨트롤의 속성을 Properties 창으로 편집하는 것이 가능합니다.
(참고로, Category를 빼면 "Misc" 범주로 포함이 됩니다.)
질문자는, enum 대신 일련의 문자열 배열을 선택 사항으로 주고 그중에 하나를 취할 수 있는 것을 원하는 듯한데요. 바로 이럴 때 사용할 수 있는 방법이 TypeConverter입니다.
우선 기본 구현을 바탕으로,
Building Windows Forms Controls and Components with Rich Design-Time Features, Part 2
; https://docs.microsoft.com/en-us/archive/msdn-magazine/2003/may/design-time-features-for-windows-forms-controls-and-components
User Control Property that shows a list of all Forms at design time
; https://stackoverflow.com/questions/58514948/user-control-property-that-shows-a-list-of-all-forms-at-design-time
특정 문자열을 반환하는 TypeConverter를 다음과 같이 작성할 수 있고,
public class UserTypeConverter : TypeConverter
{
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override bool CanConvertTo(ITypeDescriptorContext pContext, Type pDestinationType)
{
return base.CanConvertTo(pContext, pDestinationType);
}
public override object ConvertTo(ITypeDescriptorContext pContext, CultureInfo pCulture, object pValue, Type pDestinationType)
{
return base.ConvertTo(pContext, pCulture, pValue, pDestinationType);
}
public override bool CanConvertFrom(ITypeDescriptorContext pContext, Type pSourceType)
{
if (pSourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(pContext, pSourceType);
}
public override object ConvertFrom(ITypeDescriptorContext pContext, CultureInfo pCulture, object pValue)
{
if (pValue is string)
{
return pValue.ToString();
}
return base.ConvertFrom(pContext, pCulture, pValue);
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext pContext)
{
return true;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext pContext)
{
List<string> values = new List<string>();
values.AddRange(new string[] { "UserA", "UserB", "UserC" });
return new StandardValuesCollection(values);
}
}
이것을 UserControl의 속성 하나에 지정하면,
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
AccountType _userType;
[Category("MyUserControlType")]
public AccountType UserType
{
get { return _userType; }
set { _userType = value; }
}
string _guestType;
[Category("MyUserControlType")]
[TypeConverter(typeof(UserTypeConverter))]
public string GuestType
{
get { return _guestType; }
set { _guestType = value; }
}
}
다음과 같이 디자인 타임에 문자열을 선택할 수 있습니다.
그런데, 채워줄 문자열 목록을 TypeConverter에 전달해 줄 방법이 없습니다. "[TypeConverter(typeof(UserTypeConverter))]" 특성은 오직 Type 정보만을 인자로 받기 때문인데요, 채워줄 문자열들의 종류 별로 TypeConverter를 만드는 것이 영 못마땅합니다.
이에 대한 우회 방법을 찾아 보면,
How to pass parameter to TypeConverter derived class
; https://stackoverflow.com/questions/14929681/how-to-pass-parameter-to-typeconverter-derived-class
그나마 현실성 있는 방법으로 별도의 특성(Attribute)을 지정해 해결하는 것입니다. 예를 들어, TypeConverter가 선택 목록으로 보여줄 항목을 다음과 같이 StringListAttribute 특성 타입으로 전달하고,
string _guestType;
[Category("MyUserControlType")]
[TypeConverter(typeof(UserTypeConverter))]
[StringList(new string[] { "UserA", "UserB", "UserC" })]
public string GuestType
{
get { return _guestType; }
set { _guestType = value; }
}
public class StringListAttribute : Attribute
{
string[] _list;
public string [] List
{
get { return _list; }
}
public StringListAttribute(string [] list)
{
_list = list;
}
}
TypeConverter의 GetStandardValues 메서드에서 다음과 같이 해당 속성에 지정된 특성을 열거해 StringList를 찾아 그것에 설정된 목록을 반환하는 것입니다.
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext pContext)
{
List<string> values = new List<string>();
AttributeCollection ua = pContext.PropertyDescriptor.Attributes;
foreach (Attribute attr in ua)
{
if (attr is StringListAttribute listAttr)
{
values.AddRange(listAttr.List);
}
}
return new StandardValuesCollection(values);
}
그런대로 이 정도면 해결된 것 같군요. ^^
(
첨부 파일은 이 글의 소스 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]