In Windows 10, as it appears your Form uses its Theme, the behavior of scrollable UI elements sports this feature: the scroll action is dispatched to whatever control is under the Mouse Pointer, ignoring completely the Mouse Capture many Controls rely upon for their functionality.
The NativeWindow that holds the List Control's handle is not notified that the ComboBox.Location
has changed, in this scenario.
Note that the ScrollableControl Container doesn't raise Scroll events either.
You can test this Custom Control derived from the standard ComboBox.
It modifies the default behavior when its Location property is changed, base on the value of its CloseDropDownOnScroll
property:
CloseDropDownOnScroll = true:
when the Control's Location changes and the DropDown List is shown, it hides the List Control.
CloseDropDownOnScroll = false:
in the same situation, if the ComboBox and the List Control can fit in the Client Area of the Parent Container, the DropDown List is moved, to follow its ComboBox, otherwise it's hidden.
The Control uses the GetComboBoxInfo() function ti retrieve the Handle of the DropDown List Control and GetWindowRect() to get it's size.
The ListControl NativeWindow is then moved, when necessary, using the SetWindowPos() function.
It should be simple to adapt or extend its functionality, when required.
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;
}
}
▶ Don't remove this
from this.Handle
and this.Parent