【问题标题】:Capturing ComboBox item click捕获 ComboBox 项目单击
【发布时间】:2013-08-29 21:16:17
【问题描述】:

我有一个 WinForms ComboBox,它可以记住以前输入过的项目。我想要一种方法来删除以前的条目。我重写了 ComboBox 的 DrawItem 事件以呈现文本以及 X 图标。 X 图标只是一个方形图像,我将其缩放到项目的高度。代码非常简单。

// Enable the owner draw on the ComboBox.
ServerComboBox.DrawMode = DrawMode.OwnerDrawFixed;
// Handle the DrawItem event to draw the items.
ServerComboBox.DrawItem += delegate(object cmb, DrawItemEventArgs args)
{
    // Draw the default background
    args.DrawBackground();

    String url = (String)ServerComboBox.Items[args.Index];

    // Get the bounds for the first column
    Rectangle r1 = args.Bounds;
    r1.Width -= r1.Height;

    // Draw the text
    using (SolidBrush sb = new SolidBrush(args.ForeColor))
    {
        args.Graphics.DrawString(url, args.Font, sb, r1);
    }

    // Draw the X icon
    Rectangle r2 = new Rectangle(r1.Width+1, r1.Y + 1, r1.Height - 2, r1.Height - 2);
    args.Graphics.DrawImage(Project.Test.Properties.Resources.CloseIcon, r2);
};

现在我的问题是如果点击了 X,如何捕获。我的第一个想法是捕获 ComboBox 的 MouseDown 事件并检查 DroppedDown 属性是否为真,但该事件仅在您单击未展开的 ComboBox 时才会触发。如何从 ComboBox 的 DropDown 部分捕获事件。一旦我明白了,我认为确定 X 是被点击还是现在都不是什么大问题。

【问题讨论】:

    标签: c# winforms


    【解决方案1】:

    其实你的问题只是Win32可以解决的小问题:

    public class Form1 : Form {
      [DllImport("user32")]
      private static extern int GetComboBoxInfo(IntPtr hwnd, out COMBOBOXINFO comboInfo);
      struct RECT {
        public int left, top, right, bottom;
      }
      struct COMBOBOXINFO {
            public int cbSize;
            public RECT rcItem;
            public RECT rcButton;
            public int stateButton;
            public IntPtr hwndCombo;
            public IntPtr hwndItem;
            public IntPtr hwndList;
      }
      public Form1(){
        InitializeComponent();  
        comboBox1.HandleCreated += (s, e) => {
           COMBOBOXINFO combo = new COMBOBOXINFO();
           combo.cbSize = Marshal.SizeOf(combo);
           GetComboBoxInfo(comboBox1.Handle, out combo);
           hwnd = combo.hwndList;
           init = false;
        };
      }
      bool init;
      IntPtr hwnd;
      NativeCombo nativeCombo = new NativeCombo();
      //This is to store the Rectangle info of your Icons
      //Key:  the Item index
      //Value: the Rectangle of the Icon of the item (not the Rectangle of the item)
      Dictionary<int, Rectangle> dict = new Dictionary<int, Rectangle>();
      public class NativeCombo : NativeWindow {
            //this is custom MouseDown event to hook into later
            public event MouseEventHandler MouseDown;
            protected override void WndProc(ref Message m)
            {                
                if (m.Msg == 0x201)//WM_LBUTTONDOWN = 0x201
                {                    
                    int x = m.LParam.ToInt32() & 0x00ff;
                    int y = m.LParam.ToInt32() >> 16;
                    if (MouseDown != null) MouseDown(null, new MouseEventArgs(MouseButtons.Left, 1, x, y, 0));                                                                  
                }
                base.WndProc(ref m);
            }
      }
      //DropDown event handler of your comboBox1
      private void comboBox1_DropDown(object sender, EventArgs e) {
            if (!init) {
                //Register the MouseDown event handler <--- THIS is WHAT you want.
                nativeCombo.MouseDown += comboListMouseDown;
                nativeCombo.AssignHandle(hwnd);                
                init = true;
            }
      }
      //This is the MouseDown event handler to handle the clicked icon
      private void comboListMouseDown(object sender, MouseEventArgs e){
        foreach (var kv in dict) {
          if (kv.Value.Contains(e.Location)) {
             //Show the item index whose the corresponding icon was held down
             MessageBox.Show(kv.Key.ToString());
             return;
          }
        }
      }
      //DrawItem event handler
      private void comboBox1_DrawItem(object sender, DrawItemEventArgs e) {
         //We have to save the Rectangle info of the item icons
         Rectangle rect = new Rectangle(0, e.Bounds.Top, e.Bounds.Height, e.Bounds.Height);
         dict[e.Index] = rect;
         //Draw the icon
         //e.Graphics.DrawImage(yourImage, rect);   
      }
    }
    

    关于发生了什么

    ComboBox 有一个附加的下拉列表,可以通过其Handle 访问。我们使用 GetComboBoxInfo win32 api 函数来检索ComboBox 的一些信息,这些信息保存在一个名为 COMBOBOXINFO 的结构中。我们可以通过这个结构中的成员hwndList获取下拉列表Handle

    在访问 hwndList 之后。我们可以使用自定义的 NativeWindow 类(在示例中为 NativeCombo)挂钩到它的消息循环。这使我们可以轻松地干扰 下拉列表 的消息循环。然后我们可以捕获 WM_LBUTTONDOWN 消息来处理 MouseDown 事件。当然,这不是一个full MouseDown事件,但它只是一个演示,在这种情况下它足以解决问题。 WM_LBUTTONDOWN 与保存在LParam 中的点击点 的一些信息一起发送。点击点在下拉列表客户区坐标中计算(不是在屏幕坐标中)。我们应该注意到 DrawItem 事件处理程序还有 e.Bounds 参数,该参数也是在 客户区坐标(不是屏幕坐标)中计算的。因此它们在同一个坐标系中。我们使用Rectangle.Contains 方法来了解点击的点 是否包含在某个图标的边界 中。我们将所有 icon Bounds 存储在 Dictionary 中。所以包含点击点Rectangle对应的Key(存储Item index)让我们知道对应的项目索引,然后我们可以进一步处理。

    【讨论】:

    • 所有这些反引号使阅读变得困难。请谨慎使用它们。 hwndev 在当前上下文中不存在。
    • @LarsTech 谢谢,当然我在发帖之前先做了一个demo,这里有一些代码我没有复制粘贴。现已更新。
    • @KingKing 这工作得很好,除了 1 件事。进入 comboListMouseDown 的位置似乎不正确。在我原来的帖子中,图标被绘制到 r2 矩形中,它应该在客户端坐标中,所以这是我保存到字典中的矩形。 e.Location 点似乎具有正确的 Y 值,但 X 小于应有值的一半。你知道我在搞砸什么吗?
    • @TFili 我可以看到你的代码中有这个Rectangle r1 = args.Bounds;,这意味着绘图坐标是在与clicked point/ the point mouse down 的坐标相同的系统中计算的。 Dictionary&lt;int,Rectangle&gt; 用于存储图标边界的矩形。我不在乎你如何计算icon bounds,它应该可以工作。我在示例代码中使用的icon bounds 当然不适用于您的情况。点是绘图坐标和点击坐标在同一个系统中计算(基于下拉列表)。
    • @KingKing 所以问题出在 WndProc 方法中。 X & Y 被错误地提取。它不适用于非常宽的下拉菜单(大于 256 像素)。它应该是 int x = m.LParam.ToInt32() & 0xffff; int y = m.LParam.ToInt32() >> 16 & 0xffff;我通过查看 C++ 库中的 GET_X_LPARAM 和 GET_Y_LPARAM 发现了这一点。
    【解决方案2】:

    当用户选择或使用鼠标滚轮时,SelectionIndexChanged 事件将触发。如果您不想对鼠标滚轮做出反应,并且只需要对单击和选择做出反应,那么您可以考虑使用SelectionChangeCommited 事件。

    然后你可以读取SelectedIndexSelectedItem属性来获取选中的项目。

    编辑:很抱歉,我似乎完全误解了这个问题。我认为您需要在 Combo 内捕获 ListBox 的鼠标按下并使用 Rectange.Contains 手动检查它。将回来提供更多详细信息。

    【讨论】:

    • 什么ListBox?如果你指的是ComboBox的附加列表框,要访问它,对win32知之甚少,这并不容易。
    • @KingKing 我同意你的观点,这并不容易。但我也可以说没那么难。我正在尝试您提供的类似解决方案。似乎你的解决方案会奏效。如果没有,那么让我试一试。
    【解决方案3】:

    一旦用户点击了组合框中的一个项目,它就会成为选中的项目。您可以从组合框对象中检索此属性。

    【讨论】:

    • 这肯定不是 OP 想要的。
    • @KingKing 不错的挑战。继续,通过证明你可以在 winforms 中做到这一点来证明我错了。
    • @HighCore 还好我在win32 有一点关于ComboBox 的经验。看到我的回答,你可能想给我一个 +1。
    【解决方案4】:

    COMBOBOXINFO 将为您提供可用于子类化下拉列表窗口的信息。举个例子:

    https://github.com/ehosca/MRUComboBox

    【讨论】:

      猜你喜欢
      • 2011-09-22
      • 1970-01-01
      • 2023-04-03
      • 1970-01-01
      • 2010-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多