介绍 你曾经想要从你的组合盒中得到实时反馈吗?你是否曾经想让它能够让你知道用户在点击前在下拉列表中悬停的是哪个项目?在本文中,我将展示如何继承一个自定义组合框控件,当鼠标悬停在下拉列表中的项上时,该控件将触发事件。 背景 我有一个组合框的请求,它允许你更新一些东西,取决于在它的下拉列表中悬停的项目。“简单!”我想。然后,我坐下来做这件事,发现使用标准的。net控件,它比我预期的要困难一些。这是我的劳动成果……如果你知道一个更简单的方法,我很想听听! 直接进入代码! 您自己测试这一点的最简单方法是运行示例应用程序。您应该看到,此组合框与标准组合框之间的唯一区别是添加了一个新事件Hover。这是一个非常有用的事件,在用户真正选择任何新内容之前,它可以让你知道用户何时将鼠标悬停在下拉列表中的一个新项目上。有点像组合框的预览。 在代码本身中,您将注意到我们定义了自己的自定义事件参数类和一个用于处理事件的委托:
public class HoverEventArgs : EventArgs { private int _itemIndex = 0; public int itemIndex { get { return _itemIndex; } set { _itemIndex = value; } } } ... public delegate void HoverEventHandler(object sender, HoverEventArgs e);
这只是在标准EventArgs中添加了一个名为“itemIndex”的新属性,事件使用该属性来通知组合框中当前正在悬浮的项。委托接受发送方对象和由我们的自定义HoverEventArgs类提供的事件参数。 第一个真正感兴趣的地方是下面的代码部分,以及我们必须使用System.Runtime的原因。InteropServices assembly.
// Import the GetScrollInfo function from user32.dll [DllImport("user32.dll", SetLastError = true)] private static extern int GetScrollInfo(IntPtr hWnd, int n, ref ScrollInfoStruct lpScrollInfo); // Win32 constants private const int SB_VERT = 1; private const int SIF_TRACKPOS = 0x10; private const int SIF_RANGE = 0x1; private const int SIF_POS = 0x4; private const int SIF_PAGE = 0x2; private const int SIF_ALL = SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS; private const int SCROLLBAR_WIDTH = 17; private const int LISTBOX_YOFFSET = 21; // Return structure for the GetScrollInfo method [StructLayout(LayoutKind.Sequential)] private struct ScrollInfoStruct { public int cbSize; public int fMask; public int nMin; public int nMax; public int nPage; public int nPos; public int nTrackPos; }
我们需要在user32.dll中的GetScrollInfo函数来让我们知道当滚动条被用来移动它时,组合框的下拉列表部分发生了什么。要知道鼠标在屏幕上突出显示的是哪一项是相当简单的,但是要知道我们在列表上向上或向下滚动了多远则完全是另一回事,我们必须求助于这种互操作解决方案。 一旦我们完成了对所有这些的定义,我们就可以开始这个控件的主要部分:覆盖protected override void WndProc(ref Message msg)方法。 检查后看到我们实际上改变了列表中的位置(即味精)。Msg等于308),我们必须继续并捕获鼠标位置,这样我们可以确定被鼠标悬停在下拉列表上的项目。您可以通过使用光标来实现这一点。直接定位功能,但给你屏幕坐标。执行以下操作要容易得多,以便使要点相对于组合框控件:
Point LocalMousePosition = this.PointToClient(Cursor.Position); xPos = LocalMousePosition.X; yPos = LocalMousePosition.Y - this.Size.Height - 1;
一旦我们有了鼠标位置,我们就会计算在列表中从基于零的角度突出显示的项目,并将ComboBox考虑在内。项目高度和组合箱。尺寸。高度,这样调整控件中元素的大小不会破坏代码。 好的,这里是被覆盖的WndProc方法的所有荣耀:
//Capture messages coming to our combobox protected override void WndProc(ref Message msg) { //This message code indicates the value in the list is changing //32 is for DropDownStyle == Simple if ((msg.Msg == 308) || (msg.Msg == 32)) { int onScreenIndex = 0; // Get the mouse position relative to this control Point LocalMousePosition = this.PointToClient(Cursor.Position); xPos = LocalMousePosition.X; if (this.DropDownStyle == ComboBoxStyle.Simple) { yPos = LocalMousePosition.Y - (this.ItemHeight + 10); } else { yPos = LocalMousePosition.Y - this.Size.Height - 1; } // save our y position which we need to ensure the cursor is // inside the drop down list for updating purposes int oldYPos = yPos; // get the 0-based index of where the cursor is on screen // as if it were inside the listbox while (yPos >= this.ItemHeight) { yPos -= this.ItemHeight; onScreenIndex++; } //if (yPos < 0) { onScreenIndex = -1; } ScrollInfoStruct si = new ScrollInfoStruct(); si.fMask = SIF_ALL; si.cbSize = Marshal.SizeOf(si); // msg.LParam holds the hWnd to the drop down list that appears int getScrollInfoResult = 0; getScrollInfoResult = GetScrollInfo(msg.LParam, SB_VERT, ref si); // k returns 0 on error, so if there is no error add the current // track position of the scrollbar to our index if (getScrollInfoResult > 0) { onScreenIndex += si.nTrackPos; if (this.DropDownStyle == ComboBoxStyle.Simple) { simpleOffset = si.nTrackPos; } } // Add our offset modifier if we're a simple combobox since we don't // continuously receive scrollbar information in this mode. // Then make sure the item we're previewing is actually on screen. if (this.DropDownStyle == ComboBoxStyle.Simple) { onScreenIndex += simpleOffset; if (onScreenIndex > ((this.DropDownHeight / this.ItemHeight) + simpleOffset)) { onScreenIndex = ((this.DropDownHeight / this.ItemHeight) + simpleOffset - 1); } } // Check we're actually inside the drop down window that appears and // not just over its scrollbar before we actually try to update anything // then if we are raise the Hover event for this comboBox if (!(xPos > this.Width - SCROLLBAR_WIDTH || xPos < 1 || oldYPos < 0 || ((oldYPos > this.ItemHeight * this.MaxDropDownItems) && this.DropDownStyle != ComboBoxStyle.Simple))) { HoverEventArgs e = new HoverEventArgs(); e.itemIndex = (onScreenIndex > this.Items.Count - 1) ? this.Items.Count - 1 : onScreenIndex; OnHover(e); // if scrollPos doesn't equal the nPos from our ScrollInfoStruct then // the mousewheel was most likely used to scroll the drop down list // while the mouse was inside it - this means we have to manually // tell the drop down to repaint itself to update where it is hovering // still posible to "outscroll" this method but it works better than // without it present if (scrollPos != si.nPos) { Cursor.Position = new Point(Cursor.Position.X + xFactor, Cursor.Position.Y); xFactor = -xFactor; } } scrollPos = si.nPos; } // Pass on our message base.WndProc(ref msg); }
我们创建一个ScrollInfoStruct, si,来保存来自互操作调用的信息。运行这个函数可以获取滚动条的状态,我们使用滚动条来更改onScreenIndex变量,以表示在下拉列表中悬浮的实际项目。一旦我们确定哪些项目,这是我们检查鼠标的范围实际上是在下降(因为您可以滚动鼠标滚轮内当你不控制,移动它),然后如果我们,我们发送事件说悬停在一个新项。剩下的就是在包含此组合框的表单上接收事件,然后问题就解决了!当用户将鼠标悬停在自定义组合框中的新项上时,您将确切地知道。 的兴趣点 pointtoclient (Cursor.Position)对我来说是一个新东西,而且是一种获得基于鼠标位置的控件的有用方法。 非常有趣的是,下拉列表窗口的句柄在lParam参数中传递给了WndProc。很难在任何地方找到正式的文档!事实上,我从来没有…… 用鼠标滚轮移动下拉列表的内容并没有突出显示一个新项目——这似乎纯粹是基于鼠标实际移动的情况,所以我必须添加一些代码来在发生这种情况时将鼠标移动一个像素。 历史 5/29/2006 -更新工作更好与简单的下拉式风格。 5/26/2006 -第一次提交守则。 本文转载于:http://www.diyabc.com/frontweb/news340.html