프로퍼티 그리드 속성 그리드 PropertyGrid
2017/11/14 - [프로그램 자료/Visual C#] - 유저콘트롤 사용시 속성 정의 UserControl
얼마전에 이것저것 속성 정의할 일이 있어서 사용해봤던 propertygrid 콘트롤을 정리해본다.
일단 소스부터.
아래 내용은 속성을 정의했던 클래스이며, 생성자는 빠져있다.
생성자에 별 내용 없다.
유심히 봐야하는 곳은 타입 컨버터.
[TypeConverter(typeof(PropertySorter))]
public class HtmlTag
{
public void GetDataFieldList()
{
Dictionary<string, string> rtnList = new Dictionary<string, string>();
if (this.InputType == "int")
{
for (int i = 301; i < 396; i++)
rtnList.Add(string.Format("{0}({1})", string.Format("c{0:000}", i).ToUpper(), "int"), string.Format("c{0:000}", i));
}
else
{
for (int i = 396; i < 426; i++)
rtnList.Add(string.Format("{0}({1})", string.Format("c{0:000}", i).ToUpper(), "bigint"), string.Format("c{0:000}", i));
}
DataFieldConverter.List = rtnList;
}
[CategoryAttribute("\t\t\t\t\t\t\t\t\t\t객체속성"),
DisplayName("객체종류"),
PropertyOrder(1),
DescriptionAttribute("그룹박스, 텍스트박스, 체크, 콤보 등")]
[TypeConverter(typeof(TagConverter))]
public string PropTag
{
get
{
string val = string.Empty;
//tag, inputtype 가져와서 조합
if (string.IsNullOrEmpty(this.InputType))
val = this.TAG;
else
val = this.InputType;
return TagConverter.List.FindKeyByValue<string, string>(val) as string;
}
set
{
string iValue = TagConverter.List[value];
if (inputList.Contains(iValue))
{
this.TAG = "input";
this.InputType = iValue;
}
else
{
this.TAG = iValue;
this.InputType = null;
}
//datafield 콤보박스 새로 생성하여 넘김
this.DataField = null;
GetDataFieldList();
}
}
[Browsable(false)]
public string TAG { get; set; }
[Browsable(false)]
public string InputType { get; set; }
[CategoryAttribute("\t\t\t\t\t\t\t\t데이터 저장"),
DisplayName("저장칼럼"),
PropertyOrder(1),
DescriptionAttribute("TNP_DATA의 저장될 Column명")]
[TypeConverter(typeof(DataFieldConverter))]
public string DataFieldDisplay
{
get
{
return DataFieldConverter.List.FindKeyByValue<string, string>(DataField) as string;
}
set
{
this.DataField = DataFieldConverter.List[value];
}
}
[Browsable(false)]
public string DataField { get; set; }
}
/// <summary>
/// tag converter
/// </summary>
public class TagConverter : TypeConverter
{
public static Dictionary<string, string> List = new Dictionary<string, string>
{
{"그룹박스", "div" }
,{"문자열(한줄)", "text" }
,{"정수(Int)", "int" }
,{"날짜", "date" }
,{"문자열(여러줄)", "textarea" }
,{"정수(Long)", "bigint" }
,{"실수(Numeric)", "numeric" }
,{"체크박스", "check" }
,{"콤보박스", "select" }
,{"이미지", "img" }
};
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(new List<string>(List.Keys).ToArray());
}
}
/// <summary>
/// DataField converter
/// </summary>
public class DataFieldConverter : TypeConverter
{
public static Dictionary<string, string> List = new Dictionary<string, string>();
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return true;
}
public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(new List<string>(List.Keys).ToArray());
}
}
중간에 dict에서 값으로 key 를 찾는 부분이 있는데, 아래와 같이 함수를 쓰면된다.
linq 사용해서 찾으면 한줄이면 되는데, C# 2.0 버전이라 그냥 foreach로 돌린다.
public static object FindKeyByValue<Tk, Tv>(this Dictionary<Tk, Tv> dictionary, Tv value)
{
object rtn = null;
foreach (KeyValuePair<Tk, Tv> kv in dictionary)
{
if (kv.Value.Equals(value))
{
rtn = kv.Key;
break;
}
}
return rtn;
}
아래는 propertyGrid의 help? description area 높이 조절부분
private static void ChangeDescriptionHeight(PropertyGrid grid, int height)
{
if (grid == null) throw new ArgumentNullException("grid");
foreach (Control control in grid.Controls)
if (control.GetType().Name == "DocComment")
{
FieldInfo fieldInfo = control.GetType().BaseType.GetField("userSized",
BindingFlags.Instance |
BindingFlags.NonPublic);
fieldInfo.SetValue(control, true);
control.Height = height;
return;
}
}
다음 소스는 데이터의 무결성을 체크하면서 썼던 이벤트인데, 원래 여기에 사용하면 안되고 다른 곳에 valid 해주는 곳이 있을 것 같다.
우선 급한대로 작성한 걸 보자.
해당 propertygrid는 트리뷰랑 엮여있다.
private void propertyGrid1_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
{
HtmlTag tag = this.propertyGrid1.SelectedObject as HtmlTag;
var nodes = GetAllNodes(treeView);
TreeNode node;
//수정 가능한 것인지 판단
if (!SetValueByTagType(tag, e.ChangedItem.PropertyDescriptor.Name))
{
e.ChangedItem.PropertyDescriptor.SetValue(tag, e.OldValue);
propertyGrid1.Refresh();
return;
}
//validate
if (e.ChangedItem.PropertyDescriptor.Name == "DataFieldDisplay")
{
//datafield 중복 확인, 이미 변경된 상태라 2개부터 중복임
if (_list.FindAll(o => o.DataField == DataFieldConverter.List[(string)e.ChangedItem.Value]).Count > 1)
{
MessageBox.Show("중복된 필드를 사용하고 있습니다.");
tag.DataFieldDisplay = (string)e.OldValue;
return;
}
}
// PropTag 변경시 datafield 날림
if (e.ChangedItem.PropertyDescriptor.Name == "PropTag")
{
node = nodes.Find(o => o.Name == tag.ID);
if (node.Nodes.Count > 0)
{
MessageBox.Show("자식 노드가 있으면 변경이 불가능합니다.\n자식 노드를 제거 후 다시 시도해주세요.");
tag.PropTag = (string)e.OldValue;
return;
}
//노드 이미지 변경
int idx = tag.TAG == "div" ? 0 : 1;
node.ImageIndex = idx;
node.SelectedImageIndex = idx;
node.StateImageIndex = idx;
tag.DataField = null;
tag.ValidMinLength = null;
tag.ValidMaxLength = null;
tag.ValidMinNumber = null;
tag.ValidMaxNumber = null;
tag.ValidRegexString = "";
tag.ValidRegexFlag = "";
tag.TitleAlign = "";
MessageBox.Show("객체종류를 변경하면 저장칼럼을 다시 지정해주셔야합니다.");
}
//display 속성 변경시 해당 속성마다 기본값 지정
if (e.ChangedItem.PropertyDescriptor.Name == "DisplayProperty")
{
if ((string)e.ChangedItem.Value == "inline-block" && (tag.Width == "0" || tag.Width == null || tag.Width == ""))
tag.Width = "200";
else if ((string)e.ChangedItem.Value == "block")
tag.Width = null;
return;
}
//validate 숫자만 들어가야하는 곳
if (intList.Contains(e.ChangedItem.PropertyDescriptor.Name))
{
int ivalue = 0;
if (!string.IsNullOrEmpty((string)e.ChangedItem.Value) && !int.TryParse((string)e.ChangedItem.Value, out ivalue))
{
MessageBox.Show("숫자만 넣을 수 있습니다.");
e.ChangedItem.PropertyDescriptor.SetValue(tag, e.OldValue);
propertyGrid1.Refresh();
return;
}
}
//disable (해당 칼럼이 값의 변경이 없어야 된다면, e.OldValue 를 다시 할당한다
//해당 작업을 다 한 뒤에는 트리뷰, 간략모드 를 refresh 한다.
var selectedNode = nodes.Find(o => o.Name == tag.ID);
selectedNode.Text = GetTreeNodeText(tag);
RefreshPreviewDataGridView();
}
private bool SetValueByTagType(HtmlTag t, string _propertyName)
{
if (new List<string> { "Width", "Height" }.Contains(_propertyName))
return true;
else if (t.ID != "ROOT")
{
if (new List<string> { "PropTag", "Title", "DisplayProperty", "PositionDisplay", "Top", "Left" }.Contains(_propertyName))
return true;
else if (t.TAG != "div")
{
if (new List<string> { "TitleWidth", "MinWidth", "MinHeight", "DataDesc", "DataFieldDisplay", "TcVisible", "ValidErrorMsg", "TcAlignDisplay", "TcHead", "TcWidth" }.Contains(_propertyName))
return true;
else if (t.TAG == "textarea" || t.InputType == "text")
{
if (new List<string> { "ValidRegexFlagDisplay", "ValidRegexString" }.Contains(_propertyName))
return true;
else if (t.InputType == "text" && new List<string> { "UserInputTypeDisplay" }.Contains(_propertyName))
return true;
}
if (t.InputType != "check")
{
if (new List<string> { "ValidDataRequired", "TitleAlignDisplay" }.Contains(_propertyName))
return true;
if (t.InputType == "int" || t.InputType == "bigint" || t.InputType == "numeric")
{
if (new List<string> { "ValidMinNumber", "ValidMaxNumber" }.Contains(_propertyName))
return true;
}
else
{
if (new List<string> { "ValidMinLength", "ValidMaxLength" }.Contains(_propertyName))
return true;
}
if (t.TAG == "select")
if (new List<string> { "Category" }.Contains(_propertyName))
return true;
}
}
}
return false;
}