Open9

C#のPropertyGrid

forest1forest1

PropertyGrid
「すべての Windowsフォーム」にある

forest1forest1

AnchorとかDockとかみたいに専用UIを出す方法、
UITypeEditorを継承したクラスを使う、もしくは作る。

public class AnchorUITypeEditor : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
    {
        // ドロップダウンスタイルのUIを提供する
        return UITypeEditorEditStyle.DropDown;
    }

    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
    {
        // ドロップダウンスタイルのUIを作成する
        var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
        if (editorService != null)
        {
            var comboBox = new ComboBox();
            comboBox.Items.AddRange(Enum.GetNames(typeof(AnchorStyles)));
            comboBox.DropDownStyle = ComboBoxStyle.DropDownList;
            comboBox.SelectedItem = Enum.GetName(typeof(AnchorStyles), value);
            comboBox.SelectedIndexChanged += (sender, e) =>
            {
                var selectedValue = Enum.Parse(typeof(AnchorStyles), (string)comboBox.SelectedItem);
                editorService.CloseDropDown();
                context.PropertyDescriptor.SetValue(context.Instance, selectedValue);
            };
            editorService.DropDownControl(comboBox);
            return value;
        }
        return base.EditValue(context, provider, value);
    }
}

[Editor(typeof(AnchorUITypeEditor), typeof(UITypeEditor))]
public AnchorStyles Anchor { get; set; }
forest1forest1

ある項目の値を変更したら他の項目に反映させるには。
INotifyPropertyChanged でプロパティの変化をイベントで通知して、他を書き変える。

public class MyProperties
{
    private int _itemA;
    private string _itemB;
    private string _itemC;

    private PropertyDescriptorCollection _properties;

    public int ItemA
    {
        get { return _itemA; }
        set
        {
            if (_itemA != value)
            {
                _itemA = value;
                OnItemAChanged();
            }
        }
    }

    public string ItemB
    {
        get { return _itemB; }
        set
        {
            if (_itemB != value)
            {
                _itemB = value;
                OnPropertyChanged("ItemB");
            }
        }
    }

    public string ItemC
    {
        get { return _itemC; }
        set
        {
            if (_itemC != value)
            {
                _itemC = value;
                OnPropertyChanged("ItemC");
            }
        }
    }

    public MyProperties()
    {
        _properties = TypeDescriptor.GetProperties(this);
    }

    private void OnItemAChanged()
    {
        // 項目Aに対応するPropertyDescriptorオブジェクトを取得する
        var itemAProp = _properties["ItemA"];
        // 項目Bに対応するPropertyDescriptorオブジェクトを取得する
        var itemBProp = _properties["ItemB"];
        // 項目Cに対応するPropertyDescriptorオブジェクトを取得する
        var itemCProp = _properties["ItemC"];

        // 項目Bの値を設定する
        var itemBValue = $"A{_itemA}";
        itemBProp.SetValue(this, itemBValue);

        // 項目Cの値を設定する
        var itemCValue = _itemA.ToString();
        itemCProp.SetValue(this, itemCValue);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
   
public class MyProperties : INotifyPropertyChanged
{
    // ...

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    private void OnItemBChanged()
    {
        // 項目Bに対応するPropertyDescriptorオブジェクトを取得する
        var itemBProp = _properties["ItemB"];
        // 項目Cに対応するPropertyDescriptorオブジェクトを取得する
        var itemCProp = _properties["ItemC"];

        // 項目Cの値を設定する
        var itemCValue = itemBProp.GetValue(this).ToString().Substring(1);
        itemCProp.SetValue(this, itemCValue);
    }

    private void OnItemCChanged()
    {
        // 項目Cに対応するPropertyDescriptorオブジェクトを取得する
        var itemCProp = _properties["ItemC"];
        // 項目Bに対応するPropertyDescriptorオブジェクトを取得する
        var itemBProp = _properties["ItemB"];

        // 項目Bの値を設定する
        var itemBValue = $"A{itemCProp.GetValue(this)}";
        itemBProp.SetValue(this, itemBValue);
    }

    private void OnPropertyChanged(string propertyName)
    {
        switch (propertyName)
        {
            case "ItemA":
                OnItemAChanged();
                break;
            case "ItemB":
                OnItemBChanged();
                break;
            case "ItemC":
                OnItemCChanged();
                break;
        }

        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
private void Form1_Load(object sender, EventArgs e)
{
    var myProps = new MyProperties();
    propertyGrid1.SelectedObject = myProps;
    myProps.PropertyChanged += (s, args) => propertyGrid1.Refresh();
}
forest1forest1

はい、UITypeEditorを継承したクラスには、様々な種類があります。以下にいくつかの例を挙げてみます。

FontUITypeEditor
FontUITypeEditorクラスは、フォントを編集するためのエディターです。FontDialogコントロールを使用して、フォントを選択することができます。

CollectionEditor
CollectionEditorクラスは、コレクションを編集するためのエディターです。Add、Remove、Editなどの機能を提供します。プロパティグリッドでコレクションを編集する場合に使用されます。

ImageEditor
ImageEditorクラスは、画像を編集するためのエディターです。OpenFileDialogコントロールを使用して、画像を選択することができます。

FileNameEditor
FileNameEditorクラスは、ファイル名を編集するためのエディターです。OpenFileDialogまたはSaveFileDialogコントロールを使用して、ファイルを選択することができます。

DateTimeEditor
DateTimeEditorクラスは、日付と時刻を編集するためのエディターです。DateTimePickerコントロールを使用して、日付と時刻を選択することができます。

MultilineStringEditor
MultilineStringEditorクラスは、複数行の文字列を編集するためのエディターです。TextBoxコントロールを使用して、複数行のテキストを編集することができます。

なお、これらのクラスはSystem.ComponentModel.Design.dllアセンブリに含まれています。また、これらのクラスは、カスタマイズすることができます。例えば、FontUITypeEditorクラスを継承して、フォントのプレビュー機能を追加することができます。

forest1forest1

Q: 今度は、見た目は通常のテキスト入力なのですが、外部からのプロパティなど何らかの方法で設定することで、そのテキスト欄のBackColorを変えることは可能でしょうか?

ChatGPT:
はい、可能です。通常のテキストボックスを使用して、背景色を設定する方法はいくつかありますが、以下に一例を示します。

ユーザーコントロールの作成
まず、カスタムのUserControlを作成します。このUserControlに、通常のテキストボックスを配置します。その後、UserControlのBackColorプロパティを、テキストボックスのBackColorプロパティと同じに設定します。最後に、UserControlを使用して、カスタムのプロパティを作成します。

public partial class ColoredTextBox : UserControl
{
    public ColoredTextBox()
    {
        InitializeComponent();
        textBox1.BackColor = this.BackColor;
    }

    public override Color BackColor
    {
        get { return base.BackColor; }
        set
        {
            base.BackColor = value;
            textBox1.BackColor = value;
        }
    }

    public string TextValue
    {
        get { return textBox1.Text; }
        set { textBox1.Text = value; }
    }
}

このUserControlを使用して、BackColorを設定するテキストボックスを作成することができます。このテキストボックスに値を設定すると、背景色が変更されます。

プロパティのオーバーライド
次に、Controlクラスを継承したカスタムコントロールを作成し、背景色を設定するためのプロパティをオーバーライドします。ここでは、BackColorプロパティをオーバーライドして、テキストボックスのBackColorプロパティと同期させます。

public class CustomTextBox : Control
{
    private TextBox _textBox;

    public CustomTextBox()
    {
        _textBox = new TextBox();
        _textBox.Location = new Point(0, 0);
        _textBox.Size = this.ClientSize;
        _textBox.BorderStyle = BorderStyle.None;
        _textBox.TextChanged += TextBox_TextChanged;
        this.Controls.Add(_textBox);
    }

    public override Color BackColor
    {
        get { return base.BackColor; }
        set
        {
            base.BackColor = value;
            _textBox.BackColor = value;
        }
    }

    public string TextValue
    {
        get { return _textBox.Text; }
        set { _textBox.Text = value; }
    }

    private void TextBox_TextChanged(object sender, EventArgs e)
    {
        this.Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        using (var brush = new SolidBrush(_textBox.BackColor))
        {
            e.Graphics.FillRectangle(brush, _textBox.Bounds);
        }
    }
}

このカスタムコントロールを使用して、BackColorを設定するテキストボックスを作成することができます。このテキストボックスに値を設定すると、背景色が変更されます。

どちらの方法を選択するかは、使用環境や要件によって異なるかと思います。

forest1forest1

独自クラスでの値表示方法

public class MyCustomClass
{
    public int Value1 { get; set; }
    public string Value2 { get; set; }

    public override string ToString()
    {
        return string.Format("({0}, {1})", Value1, Value2);
    }
}
forest1forest1

プロパティの変数名ではなく別途指定させたい。

public class MySettings
{
    [Browsable(false)]
    public int Foo { get; set; }

    [DisplayName("Bar")]
    public string MyProperty { get; set; }
}
forest1forest1

カテゴリ名や表示名などを属性で指定しているが、言語切り替えなどの対応が難しい。
同じクラスを言語別に複製する必要があるなど。

属性を使わない方法として、ICustomTypeDescriptor を使用する。

public class MySettings : ICustomTypeDescriptor
{
    private static readonly Dictionary<string, string> displayNames = new Dictionary<string, string>
    {
        { "MyProperty", "プロパティ名" },
        { "AnotherProperty", "別のプロパティ名" }
    };

    public string MyProperty { get; set; }

    public int AnotherProperty { get; set; }

    public override string ToString()
    {
        return "My Settings";
    }

    public string GetDisplayName(string propertyName)
    {
        if (displayNames.ContainsKey(propertyName))
        {
            return displayNames[propertyName];
        }
        return propertyName;
    }

    public AttributeCollection GetAttributes()
    {
        return TypeDescriptor.GetAttributes(this, true);
    }

    public string GetClassName()
    {
        return TypeDescriptor.GetClassName(this, true);
    }

    public string GetComponentName()
    {
        return TypeDescriptor.GetComponentName(this, true);
    }

    public TypeConverter GetConverter()
    {
        return TypeDescriptor.GetConverter(this, true);
    }

    public EventDescriptor GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(this, true);
    }

    public PropertyDescriptor GetDefaultProperty()
    {
        return null;
    }

    public object GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(this, editorBaseType, true);
    }

    public EventDescriptorCollection GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(this, attributes, true);
    }

    public EventDescriptorCollection GetEvents()
    {
        return TypeDescriptor.GetEvents(this, true);
    }

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        var props = TypeDescriptor.GetProperties(this, attributes, true)
            .Cast<PropertyDescriptor>()
            .Select(pd => new MyPropertyDescriptor(pd, this))
            .ToArray();
        return new PropertyDescriptorCollection(props);
    }

    public PropertyDescriptorCollection GetProperties()
    {
        return GetProperties(null);
    }

    public object GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }
}

public class MyPropertyDescriptor : PropertyDescriptor
{
    private readonly MySettings settings;

    public MyPropertyDescriptor(PropertyDescriptor prop, MySettings settings) : base(prop)
    {
        this.settings = settings;
    }

    public override string DisplayName
    {
        get { return settings.GetDisplayName(Name); }
    }

    public override bool CanResetValue(object component)
    {
        return base.CanResetValue(component);
    }

    public override object GetValue(object component)
    {
        return base.GetValue(component);
    }

    public override void ResetValue(object component)
    {
        base.ResetValue(component);
    }
public override void SetValue(object component, object value)
{
    base.SetValue(component, value);
}

public override bool ShouldSerializeValue(object component)
{
    return base.ShouldSerializeValue(component);
}

public override Type ComponentType
{
    get { return typeof(MySettings); }
}

public override bool IsReadOnly
{
    get { return base.IsReadOnly; }
}

public override Type PropertyType
{
    get { return base.PropertyType; }
}
}
forest1forest1

属性
Category("")
DisplayName("")
Description("")
ReadOnly(bool)
Editor()

UITypeEditor継承クラス