2

我有一个 WinForms 组合框,它可以记住以前输入过的项目。我想要一种方法来删除以前的条目。我重写了 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 是否被点击或现在都不是什么大问题。

4

4 回答 4

2

实际上,您的问题只是一个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);   
  }
}

一点关于发生的事情

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

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

于 2013-08-29T22:45:05.093 回答
1

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

然后您可以读取SelectedIndexSelectedItem属性以获取所选项目。

编辑:很抱歉,我似乎完全误解了这个问题。我认为您需要ListBox在 Combo 内部捕获鼠标并使用Rectange.Contains. 将返回更多详细信息。

于 2013-08-29T21:28:01.590 回答
0

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

于 2013-08-29T21:22:23.790 回答
0

COMBOBOXINFO 将为您提供可用于子类化下拉列表窗口的信息。请参阅此示例:

https://github.com/ehosca/MRUComboBox

于 2021-07-27T10:29:46.840 回答