如何使用代码设置PropertyGrid的标签栏宽度
http://liang.eu/dotnet-development/programmatically-set-column-width-of-the-propertygrid
http://blog.csdn.net/null1/article/details/2037608
Visual Studio里自带的PropertyGrid功能强大,用好了可以省去不少工作。前几天使用到了这个控件,使用过程中发现无法控制控件里标签栏和属性值栏的宽度,Google了一番,发现网上介绍使用这个控件的文章不少,但却没有介绍如何在代码里调整PropertyGrid的标签栏和代码栏的宽度的文章。后来到CodeProject搜索了一下,发现了不少关于PropertyGrid的文章。偶e文不是很好,硬着头皮一篇一篇看了过去,最后功夫不负有心人,总算找到了几篇介绍如何调整标签栏宽度的文章:
Add Custom Properties to a PropertyGrid
.NET PropertyGrid -> How to set column width and description window height(托伟大的GFW的福,这篇文章我是通过TOR才能浏览到)
虽然是不同作者写的,但异曲同工,原理都是通过反射来调用PropertyGrid里自带的的私有成员来实现的。
那么,该如何实现这个功能呢?方法有两种,一种是设置PropertyGrid的labelWidth私有变量;另一种方法是调用PropertyGrid的MoveSplitterTo私有函数。下面详细介绍这两种方法:
第一种:直接设置 labelWidth 私有变量
labelWidth这个私有变量的用途是见名知意,我就不多说了,下面是如何使用反射来设置它的值的方法:
先新建一个类,继承于PropertyGrid,然后重写它的OnLayout方法,代码如下:
using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Reflection;为什么要有Control propertyGridView = this.Controls[2];这一行代码呢?原来,PropertyGrid控件本身是一个复合控件,它其实是有三个子窗口组成的:PropertyGridToolBar, PropertyGridView和Description Pane。其中子窗口PropertyGridView,也就是this.Controls[2](在VS的代码提示里PropertyGrid控件没有Controls这个属性,说明微软不提倡直接使用此方法,但可以手工直接输入并能正常工作),才是我们平时进行属性设置操作的地方。这里就是先获得PropertyGridView对象的引用,然后再通过反射直接设置它的labelWidth私有变量来实现设置标签栏宽度的。namespace TestProject { class PropertyGridEx:PropertyGrid { protected override void OnLayout(LayoutEventArgs e) { int width = 200; Control propertyGridView = this.Controls[2]; Type propertyGridViewType = propertyGridView.GetType();
FieldInfo fldLabelWidth = propertyGridViewType.GetField("labelWidth", BindingFlags.Instance | BindingFlags.NonPublic); fldLabelWidth.SetValue(propertyGridView, width); base.OnLayout(e); } }
}
这个方法有一个小bug:当设置的labelWidth比默认值小的话,鼠标点击PropertyGrid的属性值栏的右边不能选中该属性。如下图的AutoSizeMode属性:
正常情况下,点击图中蓝色区域内均可选中激活该属性的下拉框编辑界面,但通过上面说的方法重写OnLayout方法后,点击红色方框区域内均无法选中该属性,这是个bug的原因有空再研究研究。下面介绍另一种方法。
第二种:调用 MoveSplitterTo 私有函数
这个方法也是重写PropertyGrid的OnLayout方法,在里面通过反射调用PropertyGridView对象的MoveSplitterTo函数,这个函数是PropertyGridView对象自带的一个私有方法。我有点想不明白微软既然已经想到并实现了调整标签栏宽度这个功能,但为什么不把这个函数公开或者直接提供一个设置PropertyGrid的标签栏宽度的方法呢?
代码如下:
using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Reflection;这种方法没有第一种方法所说的那个bug,在我的项目里它运行的很好。运行结果截图如下:namespace TestProject { class PropertyGridEx:PropertyGrid { protected override void OnLayout(LayoutEventArgs e) { int width = 200; Control propertyGridView = this.Controls[2]; Type propertyGridViewType = propertyGridView.GetType();
propertyGridViewType.InvokeMember("MoveSplitterTo", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, propertyGridView, new object[] { width }); base.OnLayout(e); } }
}
演示程序:这么简单的东西,不需要演示了吧?
我希望在propertygrid输入数据时检测enter和ESC键,然后分别执行相应的动作,但是在propertygrid提供的keydown/keypress/keyup事件中输入指令后,运行时并没有执行设定的动作?
就是绑定一个普通的类,在改变其包含的某一个属性的值后,按回车键就可以执行某一个动作,而如果按ESC键就重置该类的值。
而我在propertygrid提供的keydown/keypress/keyup事件中加入的代码并不执行。
希望大家帮帮忙?
微软工程师的解答:
解答一:
以下帖子解释了为什么PropertyGrid无法对这些Key事件做出反应:http://www.thescripts.com/forum/thread631744.html
PropertyGrid has an internal grid and this is this one that
receives the keyboard events. If you really want to have a handler for
those events, you can access the internal grid (type is
System.Windows.Forms.PropertyGridInternal.Property GridView) with
myPropGrid.Controls[2]. But depending on your needs, this can be
tricky to play inside the private parts of the grid.
谢谢
Jialiang Ge
解答二:
PropertyGrid其实是有三个子窗口组成的:PropertyGridToolBar, PropertyGridView和Description Pane。其中我们在其中设置属性的子窗口是PropertyGridView。
要想获得用户按键消息,一个办法是找到PropertyGridView的handle, 然后调用SetWindowLong函数将其原来的消息处理函数替换成我们自己的函数。这样我们就能自己的函数中捕获任何发送到PropertyGridView中的消息了(SetWindowLong函数在这里其实起了钩子函数的作用)。
当然我们只是想得到按键消息,关于消息的处理,还是让原来的消息处理函数来处理。
以下是个例子:
using System.Runtime.InteropServices;
public partial class Form1 : Form
{
int GWL_WNDPROC = -4;
int WM_KEYUP = 0x101;
int VK_RETURN = 0xD;
int VK_ESCAPE = 0x1B;
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd,int nIndex,WindowProcDelegate dwNewLong);
[DllImport("user32.dll")]
static extern bool EnumChildWindows(IntPtr hWndParent, ChildWindowDelegate lpEnumFunc, string wndCaption);
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
static extern IntPtr CallWindowProc(int lpPrevWndFunc, IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
delegate bool ChildWindowDelegate(IntPtr hwndChild, string wndCaption);
delegate IntPtr WindowProcDelegate(IntPtr hwnd,int msg,IntPtr wParam,IntPtr lParam);
private bool EnumChildProc(IntPtr hwndChild, string wndCaption)
{
StringBuilder windowName = new StringBuilder(255);
GetWindowText(hwndChild, windowName, 255);
if (windowName.ToString().Equals(wndCaption))
{
expectedChild = hwndChild;
return false;
}
else
{
return true;
}
}
private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
{
if (msg == WM_KEYUP)
{
if (wParam.ToInt32() == VK_RETURN)
{
Console.WriteLine("enter key is pressed");
}
else if (wParam.ToInt32() == VK_ESCAPE)
{
Console.WriteLine("escape key is pressed");
}
}
// let the default procedure handle the Windows messages
IntPtr result = CallWindowProc(defaultProc, expectedChild, msg, wParam, lParam);
return result;
}
int defaultProc;
IntPtr expectedChild;
public Form7()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
bool result = EnumChildWindows(this.propertyGrid1.Handle, new ChildWindowDelegate(EnumChildProc), "PropertyGridView");
if (result == false)
{
defaultProc = GetWindowLong(expectedChild, GWL_WNDPROC);
SetWindowLong(expectedChild, GWL_WNDPROC, new WindowProcDelegate(WindowProc));
}
MyClass p = new MyClass();
p.ID = 1;
p.Name = "aa";
this.propertyGrid1.SelectedObject = p;
}
}
class MyClass
{
private int id;
public int ID
{
get { return id; }
set { id = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
希望对你有帮助。
刘婷
在线技术支持工程师
微软全球技术支持中心
我的测试是按照解答二的方法进行的,但是出现了错误,反馈如下:
首先谢谢你的帮助,但是我按照您的说明进行,启动后一旦PropertyGrid获得焦点,就出现错误,信息为:
未处理的“System.NullReferenceException”类型的异常出现在 system.windows.forms.dll 中。
其他信息: Object reference not set to an instance of an object.
请问是什么原因?
代码如下:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;
using System.Text;
namespace WindowsApplication1
{
/// <summary>
/// Form1 的摘要说明。
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
int GWL_WNDPROC = -4;
int WM_KEYUP = 0x101;
int VK_RETURN = 0xD;
int VK_ESCAPE = 0x1B;
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd,int nIndex,WindowProcDelegate
dwNewLong);
[DllImport("user32.dll")]
static extern bool EnumChildWindows(IntPtr hWndParent, ChildWindowDelegate
lpEnumFunc, string wndCaption);
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int
nMaxCount);
[DllImport("user32.dll")]
static extern IntPtr CallWindowProc(int lpPrevWndFunc, IntPtr hWnd, int
msg, IntPtr wParam, IntPtr lParam);
delegate bool ChildWindowDelegate(IntPtr hwndChild, string wndCaption);
delegate IntPtr WindowProcDelegate(IntPtr hwnd,int msg,IntPtr
wParam,IntPtr lParam);
private System.Windows.Forms.PropertyGrid propertyGrid1;
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();
//
// TODO: 在 InitializeComponent 调用后添加任何构造函数代码
//
}
/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows 窗体设计器生成的代码
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.propertyGrid1 = new System.Windows.Forms.PropertyGrid();
this.SuspendLayout();
//
// propertyGrid1
//
this.propertyGrid1.CommandsVisibleIfAvailable = true;
this.propertyGrid1.LargeButtons = false;
this.propertyGrid1.LineColor = System.Drawing.SystemColors.ScrollBar;
this.propertyGrid1.Location = new System.Drawing.Point(64, 56);
this.propertyGrid1.Name = "propertyGrid1";
this.propertyGrid1.Size = new System.Drawing.Size(408, 384);
this.propertyGrid1.TabIndex = 0;
this.propertyGrid1.Text = "propertyGrid1";
this.propertyGrid1.ViewBackColor = System.Drawing.SystemColors.Window;
this.propertyGrid1.ViewForeColor = System.Drawing.SystemColors.WindowText;
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(592, 566);
this.Controls.Add(this.propertyGrid1);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private bool EnumChildProc(IntPtr hwndChild, string wndCaption)
{
StringBuilder windowName = new StringBuilder(255);
GetWindowText(hwndChild, windowName, 255);
if (windowName.ToString().Equals(wndCaption))
{
expectedChild = hwndChild;
return false;
}
else
{
return true;
}
}
private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr
lParam)
{
if (msg == WM_KEYUP)
{
if (wParam.ToInt32() == VK_RETURN)
{
Console.WriteLine("enter key is pressed");
}
else if (wParam.ToInt32() == VK_ESCAPE)
{
Console.WriteLine("escape key is pressed");
}
}
IntPtr result =IntPtr.Zero;
try
{
// let the default procedure handle the Windows messages
result = CallWindowProc(defaultProc, expectedChild, msg, wParam, lParam);
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
return result;
}
int defaultProc;
IntPtr expectedChild;
private void Form1_Load(object sender, EventArgs e)
{
bool result = EnumChildWindows(this.propertyGrid1.Handle, new
ChildWindowDelegate(EnumChildProc), "PropertyGridView");
if (result == false)
{
defaultProc = GetWindowLong(expectedChild, GWL_WNDPROC);
SetWindowLong(expectedChild, GWL_WNDPROC, new
WindowProcDelegate(WindowProc));
}
MyClass p = new MyClass();
p.ID = 1;
p.Name = "aa";
this.propertyGrid1.SelectedObject = p;
}
class MyClass
{
private int id;
public int ID
{
get { return id; }
set { id = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
}
}
微软工程师的解答:
谢谢你的反馈!
我原先是在VS2005中做了测试。现在在VS.NET2003中测试了一下,确实看到了你说的问题。
这个问题的原因对于从unmanaged code到managed code的回调,在回调方法被调用时,送传递的delegate必须是active的,不能正准备垃圾回收。在我们的程序中有两个回调,一个是EnumChildWindows, 另一个是SetWindowLong。对于前者,找到PropertyGridView子窗口的handle以后就不需要再回调了,因此我们可以将所传递的ChildWindowDelegate声明为局部变量;对于后者,只要你在PropertyGrid中有任何操作,对应的回调方法都将被执行,因此所传递的WindowProcDelegate变量必须是全局的。
因此解决的办法是申明一个全局的WindowProcDelegate变量,然后传递给SetWindowLong函数。
public partial class Form1 : Form
{
...
WindowProcDelegate windowProcDelegate;
private void Form1_Load(object sender, EventArgs e)
{
windowProcDelegate = new WindowProcDelegate(WindowProc);
bool result = EnumChildWindows(this.propertyGrid1.Handle, new ChildWindowDelegate(EnumChildProc), "PropertyGridView");
if (result == false)
{
defaultProc = GetWindowLong(expectedChild, GWL_WNDPROC);
SetWindowLong(expectedChild, GWL_WNDPROC, windowProcDelegate);
}
MyClass p = new MyClass();
p.ID = 1;
p.Name = "aa";
this.propertyGrid1.SelectedObject = p;
}
}
希望对你有帮助。
刘婷
在线技术支持工程师
微软全球技术支持中心
=========================================================
至此问题解决。
未完的路:
如果按照解答一的方法进行,我目前没有测试成功,我的思路是找到PropertyGrid中的System.Windows.Forms.PropertyGridInternal.PropertyGridView子控件,然后将其KeyPress事件绑定到一个指定的处理过程上来实现需要的功能。但是我在VS2003中发现PropertyGridView并不是网上说的Controls[2],而是Controls[1],随后我进行了绑定,然而运行却没有效果,不知道为什么?
此外,我也不知道我的方法是否正确?