5

We have a windows form PropertyGrid that we use to display all the properties. We have drawn a checkbox on Boolean property that checks it self and unchecks itself based on the value. this all works fine.

the issue is, that user wants to change the check box value in single click, whereas property grid changes it on a double click and I cant figure out a way to handle clicks or change property value on single click when property type is Boolean.

How to change property value in single click?

desertnaut
  • 57,590
  • 26
  • 140
  • 166
Muds
  • 4,006
  • 5
  • 31
  • 53

4 Answers4

9

PropertyGrid internally has methods which allows us to use them with reflection to get the GridItem under mouse when you click on its PropertyGridView internal control.

In below code, I handled mouse click on its PropertyGridView control and checked if the item under mouse position is a boolean property, I reversed it's value. The event will fire for the label of property, also for icon area of the property editor:

enter image description here

PropertyGrid

using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
public class ExPropertyGrid : PropertyGrid
{
    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        var grid = this.Controls[2];
        grid.MouseClick += grid_MouseClick;
    }
    void grid_MouseClick(object sender, MouseEventArgs e)
    {
        var grid = this.Controls[2];
        var flags = BindingFlags.Instance | BindingFlags.NonPublic;
        var invalidPoint = new Point(-2147483648, -2147483648);
        var FindPosition = grid.GetType().GetMethod("FindPosition", flags);
        var p = (Point)FindPosition.Invoke(grid, new object[] { e.X, e.Y });
        GridItem entry = null;
        if (p != invalidPoint) {
            var GetGridEntryFromRow = grid.GetType()
                                          .GetMethod("GetGridEntryFromRow", flags);
            entry = (GridItem)GetGridEntryFromRow.Invoke(grid, new object[] { p.Y });
        }
        if (entry != null && entry.Value != null) {
            object parent;
            if (entry.Parent != null && entry.Parent.Value != null)
                parent = entry.Parent.Value;
            else
                parent = this.SelectedObject;
            if (entry.Value != null && entry.Value is bool) {
                entry.PropertyDescriptor.SetValue(parent,!(bool)entry.Value);
                this.Refresh();
            }
        }
    }
}

Drawing CheckBox in PropertyGrid

public class MyBoolEditor : UITypeEditor
{
    public override bool GetPaintValueSupported
        (System.ComponentModel.ITypeDescriptorContext context)
    { return true; }
    public override void PaintValue(PaintValueEventArgs e)
    {
        var rect = e.Bounds;
        rect.Inflate(1, 1);
        ControlPaint.DrawCheckBox(e.Graphics, rect, ButtonState.Flat |
            (((bool)e.Value) ? ButtonState.Checked : ButtonState.Normal));
    }
}

Class which used in screenshot

public class Model
{
    public int Property1 { get; set; }
    [Editor(typeof(MyBoolEditor), typeof(UITypeEditor))]
    public bool Property2 { get; set; }

    [TypeConverter(typeof(ExpandableObjectConverter))]
    public Model Property3 { get; set; }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Added code for *Drawing CheckBox in PropertyGrid* also *Class which used in screenshot*. – Reza Aghaei Jul 30 '16 at 00:33
  • The code is great, but does not trigger the PropertyValueChanged event. We cannot raise the PropertyValueChanged event (as it was defined in another class), but we could add a new PropertyValueChangedCheckBox event and raise it after the entry.PropertyDescriptor.SetValue line. – Cristian Scutaru Jul 20 '18 at 15:45
1

I'd like to comment, but rep isn't high enough yet. The accepted answer works great. However as mentioned the code doesn't trigger the PropertyValueChanged event.

Adding a call to OnPropertyValueChanged triggers the PropertyValueChanged event.

entry.PropertyDescriptor.SetValue(parent, !(bool)entry.Value);
this.Refresh();  
base.OnPropertyValueChanged(null);

Then in the PropertyValueChanged event code you can access the custom object that has been changed.

To communicate the changed property back to the form create some properties in the custom object, with Browsable set to false so they do not appear in the PropertyGrid.

[Browsable(false)]
public string changedParent { get; set; }
[Browsable(false)]
public string changedLabel { get; set; }
[Browsable(false)]
public string changedValue { get; set; }

At the top of the Form class create this static property

public partial class Form1 : Form
{
    private static Form1 form = null;

In the constructor of Form1 link form to this.

public Form1()
        {
        InitializeComponent();
        ..
        ..
        form = this;

Back in grid_MouseClick before triggering OnPropertyValueChanged save off the changed property information.

entry.PropertyDescriptor.SetValue(parent, !(bool)entry.Value);
this.Refresh();
                        
form.sh.changedParent = entry.Parent.Label;
form.sh.changedLabel = entry.Label;
form.sh.changedValue = entry.Value.ToString();
base.OnPropertyValueChanged(null);

Now in the PropertyValueChanged event code you can determine which property was changed.

         form.customobject.changedParent
         form.customobject.changedLabel
         form.customobject.changedValue
adent
  • 43
  • 6
0

The best answer used a reflection to get GridItem under the mouse. However, I don't see the point in doing this since it's enough to request a dedicated GridItem. Here is my implementation of MouseClick:

 void grid_MouseClick(object sender, MouseEventArgs e)
        {
            GridItem entry = SelectedGridItem;
            if (entry != null && entry.Value != null && entry.Value is bool b)
            {
                var obj = SelectedObjects.Length == 1 ? SelectedObject : SelectedObjects;
                entry.PropertyDescriptor.SetValue(obj, !b);
            }
        }
Antinet
  • 1
  • 2
0

The above code from Reza Aghaei work long time but now changed the list of controls.

void grid_MouseClick(object sender, MouseEventArgs e)
{
    var grid = this.Controls[2];  //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    var flags = BindingFlags.Instance | BindingFlags.NonPublic;
    var invalidPoint = new Point(-2147483648, -2147483648);
    /////  following line throws a Nullreference Exception
    var FindPosition = grid.GetType().GetMethod("FindPosition", flags); 
    var p = (Point)FindPosition.Invoke(grid, new object[] { e.X, e.Y });
    GridItem entry = null;
    ...
}

Now you need to select the right control(PropertyGridView). Below my solution.

int idx = -1;
for (int i = 0; i < this.Controls.Count; i++)
{
    Control control = this.Controls[i];
    if (control.Text.Contains("PropertyGridView"))
    {
      idx = i;
      break;
    }
}
var grid = this.Controls[idx];