diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KAbstractComboBox.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KAbstractComboBox.cs index 2818a81..8300883 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KAbstractComboBox.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Controls/KAbstractComboBox.cs @@ -12,7 +12,7 @@ using System.Windows.Forms; namespace Acacia.Controls { - public abstract class KAbstractComboBox : ContainerControl, IMessageFilter + public abstract class KAbstractComboBox : ContainerControl { #region Properties @@ -79,8 +79,6 @@ namespace Acacia.Controls _edit.PreviewKeyDown += _edit_PreviewKeyDown; _edit.Leave += _edit_Leave; _edit.Enter += _edit_Enter; - - Application.AddMessageFilter(this); } private void _edit_Enter(object sender, EventArgs e) @@ -94,12 +92,6 @@ namespace Acacia.Controls DroppedDown = false; } - protected override void OnHandleDestroyed(EventArgs e) - { - Application.RemoveMessageFilter(this); - base.OnHandleDestroyed(e); - } - #endregion #region Text edit @@ -167,56 +159,125 @@ namespace Acacia.Controls #region Drop down - private class DropDownRenderer : ToolStripRenderer + /// + /// Custom drop down. Registers a message filter when shown to close on clicks outside the dropdown. + /// This is required as the default AutoClose behaviour consumes all keyboard events. + /// + private class DropDown : ToolStripDropDown, IMessageFilter { - private readonly KVisualStyle.Part _style; - - public DropDownRenderer(KVisualStyle.Part style) + /// + /// Custom renderer that renders the border using the combo focus style. + /// + private class DropDownRenderer : ToolStripRenderer { - this._style = style; + private readonly KVisualStyle.Part _style; + + public DropDownRenderer(KVisualStyle.Part style) + { + this._style = style; + } + + protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) + { + _style.DrawBackground(e.Graphics, State.Pressed, e.AffectedBounds); + } } - protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) - { - _style.DrawBackground(e.Graphics, State.Pressed, e.AffectedBounds); - } - } + private readonly KAbstractComboBox _owner; - private class DropDown : ToolStripDropDown - { - public DropDown(DropDownRenderer renderer) + public DropDown(KAbstractComboBox owner) { - Renderer = renderer; + this._owner = owner; + + KVisualStyle.Part style = owner._style[COMBOBOXPARTS.CP_BORDER]; + Renderer = new DropDownRenderer(style); + using (Graphics graphics = CreateGraphics()) + { + Padding = style?.GetMargins(graphics, State.Pressed) ?? new Padding(); + } + + Margin = new Padding(0); + AutoSize = true; + DropShadowEnabled = false; + AutoClose = false; + + // Add a host for the control + ToolStripControlHost host = new ToolStripControlHost(owner._dropControl); + host.Padding = new Padding(0); + host.Margin = new Padding(0); + host.AutoSize = true; + Items.Add(host); + } + + protected override void OnVisibleChanged(EventArgs e) + { + base.OnVisibleChanged(e); + + if (Visible) + Application.AddMessageFilter(this); + else + Application.RemoveMessageFilter(this); + } + + public bool PreFilterMessage(ref Message m) + { + switch ((WM)m.Msg) + { + case WM.LBUTTONDOWN: + case WM.RBUTTONDOWN: + case WM.MBUTTONDOWN: + return CheckMouseDown(m, false); + case WM.NCLBUTTONDOWN: + case WM.NCRBUTTONDOWN: + case WM.NCMBUTTONDOWN: + return CheckMouseDown(m, true); + } + return false; + } + + private bool CheckMouseDown(Message m, bool nonClient) + { + Point pt = User32.GetPointLParam(m.LParam); + Point ptOrig = pt; + if (!nonClient) + { + // Map to global coordinates, non-client ones already are + User32.MapWindowPoints(m.HWnd, IntPtr.Zero, ref pt, 1); + } + + // Check if the click was inside the dropdown + if (!Bounds.Contains(pt)) + { + // Outside the dropdown, check if it was inside the combo box + + // Map to the combo box coordinates + User32.MapWindowPoints(IntPtr.Zero, _owner.Handle, ref pt, 1); + if (_owner.ClientRectangle.Contains(pt)) + { + // Clicked inside the combo box. If the click was on the button, return true to prevent opening + // the popup again. + if (_owner._stateButton.Rectangle.Contains(pt)) + { + return true; + } + } + else + { + // Outside the dropdown, close it + Close(); + } + } + return false; } } private DropDown _dropDown; private Control _dropControl; - private ToolStripControlHost _dropListHost; private void SetupDropDown() { - _dropListHost = new ToolStripControlHost(_dropControl); - - _dropListHost.Padding = new Padding(0); - _dropListHost.Margin = new Padding(0); - _dropListHost.AutoSize = true; - _dropListHost.GotFocus += (s, e) => System.Diagnostics.Trace.WriteLine("_dropListHost.GotFocus"); - - _dropDown = new DropDown(new DropDownRenderer(_style[COMBOBOXPARTS.CP_BORDER])); - _dropDown.Padding = new Padding(0); - using (Graphics graphics = CreateGraphics()) - { - Padding insets = _style[COMBOBOXPARTS.CP_BORDER]?.GetMargins(graphics, State.Hot) ?? new Padding(); - _dropDown.Padding = insets; - } - _dropDown.Margin = new Padding(0); - _dropDown.AutoSize = true; - _dropDown.DropShadowEnabled = false; - _dropDown.Items.Add(_dropListHost); + _dropDown = new DropDown(this); _dropDown.Closed += _dropDown_Closed; - _dropDown.AutoClose = false; - _dropDown.GotFocus += (s, e) => System.Diagnostics.Trace.WriteLine("_dropDown.GotFocus"); } // Cannot use visibility of _dropDown to keep the open state, as clicking on the button already @@ -230,6 +291,7 @@ namespace Acacia.Controls private void Button_Clicked() { + System.Diagnostics.Trace.WriteLine("Button_Clicked"); DroppedDown = !DroppedDown; this._edit.Focus(); } @@ -248,23 +310,7 @@ namespace Acacia.Controls { if (value) { - // Calculate the dimensions of the dropdown - int maxHeight = GetDropDownHeightMax(); - int minHeight = GetDropDownHeightMin(); - //Size prefSize = new Size(minHeight, maxHeight); - Size prefSize = _dropControl.GetPreferredSize(new Size(Width - _dropDown.Padding.Horizontal, maxHeight - _dropDown.Padding.Vertical)); - int width = Util.Bound(prefSize.Width, Width - _dropDown.Padding.Horizontal, Width * 2); - int height = Util.Bound(prefSize.Height, minHeight, maxHeight); - - System.Diagnostics.Trace.WriteLine(string.Format("DROPDOWN1: {0} - {1} - {2}", prefSize, width, - ((ListBox)_dropControl).ItemHeight)); - _dropControl.MaximumSize = _dropControl.MinimumSize = new Size(width, height); - - // Show the drop down below the current control - _dropDown.Show(this.PointToScreen(new Point(0, Height - 1))); - //_dropListHost.Height = _dropDown.Height - _dropDown.Padding.Vertical; - System.Diagnostics.Trace.WriteLine(string.Format("DROPDOWN2: {0} - {1} - {2} - {3}: {4}", - _dropDown.Width, _dropListHost.Width, _dropControl.Width, width, this.Width)); + ShowDropDown(); } else { @@ -275,6 +321,27 @@ namespace Acacia.Controls } } + private void ShowDropDown() + { + // Calculate the dimensions of the dropdown + int maxHeight = GetDropDownHeightMax(); + int minHeight = GetDropDownHeightMin(); + //Size prefSize = new Size(minHeight, maxHeight); + Size prefSize = _dropControl.GetPreferredSize(new Size(Width - _dropDown.Padding.Horizontal, maxHeight - _dropDown.Padding.Vertical)); + int width = Util.Bound(prefSize.Width, Width - _dropDown.Padding.Horizontal, Width * 2); + int height = Util.Bound(prefSize.Height, minHeight, maxHeight); + + System.Diagnostics.Trace.WriteLine(string.Format("DROPDOWN1: {0} - {1} - {2}", prefSize, width, + ((ListBox)_dropControl).ItemHeight)); + _dropControl.MaximumSize = _dropControl.MinimumSize = new Size(width, height); + + // Show the drop down below the current control + _dropDown.Show(this.PointToScreen(new Point(0, Height - 1))); + //_dropListHost.Height = _dropDown.Height - _dropDown.Padding.Vertical; + System.Diagnostics.Trace.WriteLine(string.Format("DROPDOWN2: {0} - {1} - {2} - {3}: {4}", + _dropDown.Width, 0, _dropControl.Width, width, this.Width)); + } + protected abstract int GetDropDownHeightMax(); protected abstract int GetDropDownHeightMin(); @@ -390,60 +457,5 @@ namespace Acacia.Controls } #endregion - - #region Message filtering - - public bool PreFilterMessage(ref Message m) - { - switch ((WM)m.Msg) - { - case WM.LBUTTONDOWN: - case WM.RBUTTONDOWN: - case WM.MBUTTONDOWN: - return CheckMouseDown(m, false); - case WM.NCLBUTTONDOWN: - case WM.NCRBUTTONDOWN: - case WM.NCMBUTTONDOWN: - return CheckMouseDown(m, true); - - } - return false; - } - - private bool CheckMouseDown(Message m, bool nonClient) - { - if (_dropDown.Visible) - { - // - // When a mouse button is pressed, we should determine if it is within the client coordinates - // of the active dropdown. If not, we should dismiss it. - // - int i = unchecked((int)(long)m.LParam); - short x = (short)(i & 0xFFFF); - short y = (short)((i >> 16) & 0xffff); - Point pt = new Point(x, y); - Point ptOrig = pt; - if (!nonClient) - { - // Map to global coordinates - User32.MapWindowPoints(m.HWnd, IntPtr.Zero, ref pt, 1); - } - System.Diagnostics.Trace.WriteLine(string.Format("MOUSE: {0} - {1} - {2}", pt, _dropDown.Bounds, ptOrig)); - if (!_dropDown.Bounds.Contains(pt)) - { - System.Diagnostics.Trace.WriteLine(string.Format("CONTAINS1")); - User32.MapWindowPoints(m.HWnd, Handle, ref pt, 1); - if (!ClientRectangle.Contains(pt)) - { - DroppedDown = false; - return false; - } - } - } - return false; - } - - #endregion - } } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/User32.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/User32.cs index c8f0687..9a00021 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/User32.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Native/User32.cs @@ -78,6 +78,21 @@ namespace Acacia.Native [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)] public static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, [In, Out] ref Point pt, int cPoints); + public static int GetXLParam(IntPtr lParam) + { + return lParam.ToInt32() & 0xFFFF; + } + + public static int GetYLParam(IntPtr lParam) + { + return (lParam.ToInt32() >> 16) & 0xFFFF; + } + + public static Point GetPointLParam(IntPtr lParam) + { + return new Point(GetXLParam(lParam), GetYLParam(lParam)); + } + #endregion #region Messages