65.9K
CodeProject 正在变化。 阅读更多。
Home

C# PropertyGrid 自定义属性的上下文菜单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (9投票s)

2006 年 1 月 29 日

CPOL

3分钟阅读

viewsIcon

151110

downloadIcon

1237

C# PropertyGrid 中自定义属性的上下文菜单,允许用户重置属性,操作方式与 Visual Studio 相同。

里面有另一个窗口

There is another window inside

不仅改变值,还改变名称

Changing the name also, not only the value

引言

我需要允许用户重置属性网格中的值,就像 Visual Studio 中那样(右键单击属性时使用上下文菜单)。这对于某些原因很有用

  • 您不必记住属性的旧值。控件会为您完成所有操作。
  • 控件使用粗体显示已修改的属性,因此很容易看到哪些属性已被修改。

背景

在互联网上搜索了大量内容后,我仍然一无所获。然后我想到使用与 Visual Studio 捆绑在一起的旧工具 Spy++。然后我注意到,在看似属性网格的内部还有另一个窗口。那个窗口完成了使属性网格如此出色的所有令人印象深刻的工作。

由于没有直接的方法可以访问那个窗口,我不得不对其进行强力破解

propertyGrid1.GetChildAtPoint( new Point(40,40) )

现在,我有了这个窗口。我所要做的就是获取窗口收到的消息,并寻找指示右键单击的消息。

这是通过向应用程序循环中的所有消息添加过滤器,并查找寻址到我们窗口的消息来完成的

Application.AddMessageFilter( new PropertyGridMessageFilter( 
        propertyGrid1.GetChildAtPoint( new Point(40,40) ), 
        new MouseEventHandler( propGridView_MouseUp )) );

由于在 OnLoad 事件处理程序中执行此操作失败(未将对象引用设置为对象的实例),我现在在定时器事件处理程序中执行此操作,该处理程序允许整个 GUI 实例化

private void timerLoad_Tick(object sender, System.EventArgs e)
{
    timerLoad.Stop();

    // this fails if called directly in OnLoad
    // since the control didn't finish creating itself: 
    Application.AddMessageFilter( 
        new PropertyGridMessageFilter( 
            propertyGrid1.GetChildAtPoint( new Point(40,40) ),
        new MouseEventHandler( propGridView_MouseUp )) );
}

过滤器查找 WM_?BUTTONUP 消息,并调用提供的委托

public bool PreFilterMessage(ref Message m)
{
    if( ! this.Control.IsDisposed && m.HWnd == 
          this.Control.Handle && MouseUp != null)
    {
        MouseButtons mb = MouseButtons.None;
        
        switch( m.Msg )
        {
            case  0x0202 : /*WM_LBUTTONUP, see winuser.h*/
                mb = MouseButtons.Left;
                break;
            case  0x0205 : /*WM_RBUTTONUP*/
                mb = MouseButtons.Right;
                break;
        }

        if( mb != MouseButtons.None )
        {
            MouseEventArgs e = 
            new MouseEventArgs( mb, 1, m.LParam.ToInt32() & 0xFFff, 
                                m.LParam.ToInt32() >> 16, 0 );
        
            MouseUp( Control, e );
        }
    }
    return false;
}

当表单的方法被过滤器调用时,我只需获取 propertyGrid1.SelectedGridItem.PropertyDescriptor 并显示包含“重置”和“更改名称”选项的上下文菜单。

在这里,您可以看到“重置”选项显示默认值,以便用户可以并排查看新值和默认值

reset

如果用户想重命名属性本身呢

首先,程序会显示一个现场文本框,供用户更改属性名称

/// <SUMMARY>
/// This is a special feature to allow renaming of the property itself
/// </SUMMARY>
private void menuItemChangeName_Click(object sender, System.EventArgs e)
{
    GenericPropertyDescriptor gpd = 
        propertyGrid1.SelectedGridItem.PropertyDescriptor 
        as GenericPropertyDescriptor;
    if( gpd != null )
    {
        TextBox t = new TextBox();

        t.Text = gpd.Property.PropertyName;
        // so we know what property is about 
        t.Tag = gpd;
        // to capture Enter & Escape 
        t.KeyUp += new KeyEventHandler(textbox_name_KeyUp);
        // to allow hidding of the textbox
        // by clicking somewhere else: 
        t.MouseUp += new MouseEventHandler(textbox_name_MouseUp); 
        // i want all the mouse messages
        // to be sent to the textbox 
        t.Capture = true;

        // placing the textbox in the form,
        // right under the mouse: 
        Point mp = MousePosition; mp.Offset( -3, -3 );
        t.Location = PointToClient( mp );
        Controls.Add( t );
        t.BringToFront();
        t.Focus();

        // clears the initial selection of the text 
        t.Select( t.Text.Length, 0 );
        
        // making the control obvious to the user: 
        t.BackColor = Color.Yellow;
        t.ForeColor = Color.Red;
    }
}

一旦文本框显示在屏幕上,就可以通过按 Enter 键验证其内容,或通过按 Escape 键将其丢弃

private void textbox_name_KeyUp(object sender, KeyEventArgs e)
{
    switch( e.KeyData )
    {
        case Keys.Enter:
        {// keep the changes: 
            TextBox t = sender as TextBox;
            ( (GenericPropertyDescriptor) 
               t.Tag ).Property.PropertyName = t.Text != "" ? 
               t.Text : "must type something or" + 
               " the control will go berserk" ;

            RemoveTextBox( sender );

            // refresh the grid since it can't
            // possibly know we changed something: 
            propertyGrid1.Refresh(); 
        }break;

        case Keys.Escape:
        {// just "go home" 
            RemoveTextBox( sender );
        }break;
    }
}

使用代码

属性存储在实现 ICustomTypeDescriptor 的集合中(这已经在许多文章中解释过了,我在这里不再解释)。当属性网格询问时,此集合返回自定义属性 (propertyGrid1.SelectedObject = properties;)。将属性添加到此集合非常容易

properties = new GenericPropertyCollection_CustomTypeDescriptor();
properties.AddProperty( new GenericProperty( "Integer", 
                       (int) 1, "Custom", "Int32" ) );
properties.AddProperty( new GenericProperty( "Float disabled", 
                        4.5f, "Custom", "Single", 
                        new ReadOnlyAttributeEditor() ) );

使其中一个属性只读(禁用)也很容易,只需使用 ReadOnlyAttributeEditor 进行标记即可。

该集合可用作数据存储,不需要并行结构。可以这样访问数据

foreach( GenericProperty gp in properties )
    MessageBox.Show( gp.Value.ToString() );

关注点

通过访问操作系统或通用控件的内部,可以完成许多事情。可以使用 Platform Invoke 和消息过滤来创建新的、有用的和原创的效果。

我发现使用当用户完成操作时消失的小文本框来现场编辑属性名称很有趣。这有点像 Visual Studio 中放置在所有控件(工具栏、编辑窗口)上的撤消控件。

历史

  • 2006 年 1 月 29 日 - 第一个版本。
© . All rights reserved.