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