【问题标题】:DropDown List separated from the ComboBox in a scrollable Panel下拉列表与可滚动面板中的组合框分开
【发布时间】:2021-09-01 14:43:21
【问题描述】:

如果我将 ComboBox 放在 Panel 内并将 Panel 的 AutoScroll 属性设置为 True,则滚动面板时不会重绘 ComboBox 列表。这导致 DropDown 浮动在其初始位置并覆盖其他控件。可以通过以下方式轻松重现此问题:

  1. 创建一个新的 Windows 窗体项目
  2. 在表单中放置一个面板(使其足够大以容纳几个 ComboBox)
  3. 垂直堆叠放置一些组合框(添加了一些项目),以便它们放置在面板边界之外(这将使滚动条出现在设计器中)
  4. 运行项目并单击其中一个组合框以显示下拉菜单
  5. 将鼠标移动到面板上的某个位置并滚动鼠标滚轮

当您滚动时,您会注意到 ComboBox 会上下移动,但 ComboBox 下拉列表会保留在之前绘制的位置。

我认为这是 Windows 窗体中的一个错误,但我在任何地方都找不到它的文档。

是否有针对此问题的解决方法,因此我不会出现此行为?

【问题讨论】:

    标签: .net winforms combobox panel autoscroll


    【解决方案1】:

    在 Windows 10 中,当您的窗体使用其主题时,可滚动 UI 元素的行为体现了这一功能:滚动操作被调度到鼠标指针下的任何控件,完全忽略许多控件的功能都依赖于鼠标捕获。

    在这种情况下,不会通知持有列表控件句柄的 NativeWindow ComboBox.Location 已更改。
    请注意,ScrollableControl Container 也不会引发 Scroll 事件。


    您可以测试从标准 ComboBox 派生的自定义控件。
    它根据其 CloseDropDownOnScroll 属性的值修改其 Location 属性更改时的默认行为:

    • CloseDropDownOnScroll = true: 当控件的位置发生变化并显示下拉列表时,它会隐藏列表控件。

    • CloseDropDownOnScroll = false: 在同样的情况下,如果 ComboBox 和 List Control 可以放入 Parent Container 的客户区,则 DropDown List 被移动,跟随其 ComboBox,否则隐藏。

    控件使用GetComboBoxInfo() 函数来检索下拉列表控件的句柄和GetWindowRect() 来获取它的大小。

    然后在必要时使用 SetWindowPos() 函数移动 ListControl NativeWindow。
    在需要时调整或扩展其功能应该很简单。

    using System.ComponentModel;
    using System.Drawing;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    [DesignerCategory("code")]
    public class ComboBoxExt : ComboBox
    {
        IntPtr listHandle = IntPtr.Zero;
        private Size listSize = Size.Empty;
    
        public ComboBoxExt() { }
    
        public bool CloseDropDownOnScroll { get; set; } = true;
    
        protected override void OnHandleCreated(EventArgs e)
        {
            base.OnHandleCreated(e);
            listHandle = GetComboBoxListInternal(this.Handle);
            if (GetWindowRect(listHandle, out Rectangle rect)) {
                listSize = rect.Size;
            }
        }
    
        protected override void OnLocationChanged(EventArgs e)
        {
            base.OnLocationChanged(e);
    
            if (!DesignMode && DroppedDown) {
                if (CloseDropDownOnScroll) {
                    DroppedDown = false;
                }
                else {
                    var rect = RectangleToScreen(ClientRectangle);
                    if (Bottom > 0 && (Bottom + listSize.Height) < this.Parent.ClientSize.Height) {
                        SetWindowPos(listHandle, IntPtr.Zero, rect.Left, rect.Bottom, 0, 0, swpflags);
                    }
                    else {
                        DroppedDown = false;
                    }
                }
            }
        }
    
        private const uint SWP_NOSIZE = 0x0001;
        private const uint SWP_NOZORDER = 0x0004;
        private const uint SWP_ASYNCWINDOWPOS = 0x4000;
        uint swpflags = SWP_NOSIZE | SWP_NOZORDER | SWP_ASYNCWINDOWPOS;
    
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        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, uint uFlags);
    
        [DllImport("user32.dll", SetLastError = true)]
        internal static extern bool GetWindowRect(IntPtr hwnd, out Rectangle lpRect);
    
        [StructLayout(LayoutKind.Sequential)]
        internal struct COMBOBOXINFO
        {
            public int cbSize;
            public Rectangle rcItem;
            public Rectangle rcButton;
            public int buttonState;
            public IntPtr hwndCombo;
            public IntPtr hwndEdit;
            public IntPtr hwndList;
            public void Init() => this.cbSize = Marshal.SizeOf<COMBOBOXINFO>();
        }
    
        internal static IntPtr GetComboBoxListInternal(IntPtr cboHandle)
        {
            var cbInfo = new COMBOBOXINFO();
            cbInfo.Init();
            GetComboBoxInfo(cboHandle, ref cbInfo);
            return cbInfo.hwndList;
        }
    }
    

    ▶ 不要从this.Handlethis.Parent 中删除this

    【讨论】:

      【解决方案2】:

      它也发生在Windows 8.1。一种方法是检测Location 何时更改,然后重新显示下拉菜单。注意:LocationChanged 可能不会被触发,具体取决于正在滚动的父控件。

          Application.EnableVisualStyles();
          Application.SetCompatibleTextRenderingDefault(false);
      
          Form f5 = new Form();
          ComboBox combo5 = new ComboBox();
          combo5.Margin = new Padding(50, 100, 0, 0);
          combo5.Items.AddRange(new Object[] { "A", "B", "C" });
          FlowLayoutPanel panel = new FlowLayoutPanel();
          panel.Controls.Add(combo5);
          f5.Controls.Add(panel);
          combo5.LocationChanged += delegate {
              if (combo5.DroppedDown) {
                  // this could be replaced with better code that grabs the handle of the list box window
                  // and then sets the location of the list box
                  combo5.DroppedDown = false;
                  combo5.DroppedDown = true;
              }
          };
          panel.AutoScroll = true;
          panel.AutoScrollMinSize = new Size(500, 500);
          panel.Dock = DockStyle.Fill;
          Application.Run(f5);
      

      【讨论】:

        猜你喜欢
        • 2014-05-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多