我正在尝试制作一个自定义组合框。我想在组合框中有一个按钮,以便用户可以将项目添加到其数据源。 我知道我需要使用 WndProc 才能获取内部文本框,但我不确定如何获取它,然后编辑它的长度以在选择时不覆盖按钮。
现在,当我将鼠标悬停在组合框上或输入它进行编辑测试时,按钮被内部文本框编辑控件覆盖。
这就是我输入时组合框的样子
这就是我想要的组合框的样子
我的代码绝对是一团糟,因为我试图弄清楚这一点,但我并没有真正取得任何进展。 我会说我已将按钮控件添加到组合框,因为我想访问内置功能。
public ComboBoxButton() : base()
{
_button1.Size = buttonSize;
this.DrawMode = DrawMode.OwnerDrawVariable;
_button1.BackColor = SystemColors.Control;
_button1.Text = "?";
_button1.Location = buttonLocation;
this.Controls.Add(_button1);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
}
我尝试了几种不同的方法来通过
获得编辑控制权[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
我还找到了这段代码,并希望我可以编辑它来修改文本框大小,但无法
private class TextWindow : NativeWindow
{
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
private struct COMBOBOXINFO
{
public Int32 cbSize;
public RECT rcItem;
public RECT rcButton;
public int buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
[DllImport("user32.dll", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessageCb(IntPtr hWnd, int msg, IntPtr wp, out COMBOBOXINFO lp);
public TextWindow(ComboBox cb)
{
COMBOBOXINFO info = new COMBOBOXINFO();
info.cbSize = Marshal.SizeOf(info);
SendMessageCb(cb.Handle, 0x164, IntPtr.Zero, out info);
this.AssignHandle(info.hwndEdit);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == (0x0302))
{
MessageBox.Show("No pasting allowed!");
return;
}
base.WndProc(ref m);
}
}
private TextWindow textWindow;
protected override void OnHandleCreated(EventArgs e)
{
textWindow = new TextWindow(this);
base.OnHandleCreated(e);
listBoxHandle = GetComboBoxListInternal(this.Handle, out COMBOBOXINFO info);
}
protected override void OnHandleDestroyed(EventArgs e)
{
textWindow.ReleaseHandle();
base.OnHandleDestroyed(e);
}
要重新定位自定义组合框的编辑控件,您可以调用 GetComboBoxInfo,它返回一个 COMBOBOXINFO 结构,该结构引用编辑控件(以及箭头按钮)的句柄和位置。
您现在正在发送
CB_GETCOMBOBOXINFO
消息;同样的事情,你会得到同样的信息。
将自定义按钮添加到组合框时,您需要考虑其宽度、箭头按钮的宽度,并从编辑控件的当前宽度中减去这些测量值。
在示例代码中,这是在首次创建 ComboBox 时以及当控件收到 WM_WINDOWPOSCHANGED 消息时完成的,该消息告诉您控件已调整大小。然后,您可以重新定位自定义按钮,并相应地调整编辑控件的大小。
为此,您可以调用 SetWindowPos 来设置编辑控件的新大小(请注意,该大小不能代表任意尺寸;子控件必须覆盖 ComboBox 的整个表面。其背景实际上并未绘制).
自定义按钮单击时会引发公共事件 (
CustomButtonClicked
)。您可以照常在父表单中订阅此事件。查看您的组合框的事件。
当 ComboBox 收到
WM_DESTROY
消息时,自定义 Button 将被释放,并且其事件将被取消订阅。
如果不能使用可空值,只需删除
?
,如 Button? customButton = null;
using System.ComponentModel;
using System.Runtime.InteropServices;
public class ComboBoxWithButton : ComboBox {
Button? customButton = null;
public event EventHandler<EventArgs>? CustomButtonClicked;
public ComboBoxWithButton() {
customButton = new Button() { Text = "?", FlatStyle = FlatStyle.Flat, Parent = this };
customButton.FlatAppearance.BorderSize = 0;
customButton.Click += CustomButton_Click;
}
[DefaultValue(8)]
protected internal int EditControlOffset { get; set; } = 4;
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
UpdateControlsPosition();
}
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
switch (m.Msg) {
case WM_WINDOWPOSCHANGED:
UpdateControlsPosition();
break;
case WM_DESTROY:
if (customButton != null) {
customButton.Click -= CustomButton_Click;
customButton.Dispose();
}
break;
default:
break;
}
}
private void UpdateControlsPosition() {
if (customButton != null) {
SuspendLayout();
Rectangle arrowRect = GetComboBoxInfoInternal(Handle, out Rectangle editRect, out IntPtr editHwnd);
if (arrowRect.IsEmpty || editRect.IsEmpty) return;
// Same size as the Arrow Button: change as required
customButton.Size = arrowRect.Size;
customButton.Location = new Point(arrowRect.Left - customButton.Width - 1, 1);
editRect.Width = ClientSize.Width - customButton.Width * 2 - EditControlOffset;
SetWindowPos(editHwnd, IntPtr.Zero, 0, 0, editRect.Width, editRect.Height, SWP_Flags);
ResumeLayout(false);
}
}
private void CustomButton_Click(object? sender, EventArgs e) => OnEditButtonClicked(e);
protected virtual void OnEditButtonClicked(EventArgs e) {
CustomButtonClicked?.Invoke(this, e);
}
const int SWP_NOMOVE = 0x0002;
const int SWP_NOZORDER = 0x0004;
const int SWP_SHOWWINDOW = 0x0040;
const int SWP_Flags = SWP_NOMOVE | SWP_NOZORDER;
const int WM_DESTROY = 0x0002;
const int WM_WINDOWPOSCHANGED = 0x0047;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, int uFlags);
[StructLayout(LayoutKind.Sequential)]
internal struct RECT {
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(int left, int top, int right, int bottom) {
Left = left; Top = top; Right = right; Bottom = bottom;
}
public static RECT FromRectangle(Rectangle rect) => new RECT(rect.X, rect.Y, rect.Right, rect.Bottom);
public readonly Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, Bottom);
}
[StructLayout(LayoutKind.Sequential)]
internal struct COMBOBOXINFO {
public int cbSize;
public RECT rcItem;
public RECT rcButton;
public int buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
//public COMBOBOXINFO() => cbSize = Marshal.SizeOf<COMBOBOXINFO>();
}
internal static Rectangle GetComboBoxInfoInternal(IntPtr cboHandle, out Rectangle editRect, out IntPtr editHandle) {
var cbInfo = new COMBOBOXINFO() { cbSize = Marshal.SizeOf<COMBOBOXINFO>() };
GetComboBoxInfo(cboHandle, ref cbInfo);
editHandle = cbInfo.hwndEdit;
editRect = cbInfo.rcItem.ToRectangle();
return cbInfo.rcButton.ToRectangle();
}
}