向组合框添加按钮并调整内部文本框编辑控件的大小

问题描述 投票:0回答:1

我正在尝试制作一个自定义组合框。我想在组合框中有一个按钮,以便用户可以将项目添加到其数据源。 我知道我需要使用 WndProc 才能获取内部文本框,但我不确定如何获取它,然后编辑它的长度以在选择时不覆盖按钮。

现在,当我将鼠标悬停在组合框上或输入它进行编辑测试时,按钮被内部文本框编辑控件覆盖。

这就是我输入时组合框的样子

Button Covered

这就是我想要的组合框的样子

How I want the button to look

我的代码绝对是一团糟,因为我试图弄清楚这一点,但我并没有真正取得任何进展。 我会说我已将按钮控件添加到组合框,因为我想访问内置功能。

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);
}
c# .net winforms combobox user-controls
1个回答
0
投票

要重新定位自定义组合框的编辑控件,您可以调用 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();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.