mirror of
https://github.com/Kopano-dev/kopano-ol-extension.git
synced 2023-10-10 13:37:40 +02:00
Finalised mouse handling for combo box
This commit is contained in:
parent
5371e0ee43
commit
a0640739e8
@ -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
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private class DropDown : ToolStripDropDown, IMessageFilter
|
||||
{
|
||||
private readonly KVisualStyle<COMBOBOXPARTS, State>.Part _style;
|
||||
|
||||
public DropDownRenderer(KVisualStyle<COMBOBOXPARTS, State>.Part style)
|
||||
/// <summary>
|
||||
/// Custom renderer that renders the border using the combo focus style.
|
||||
/// </summary>
|
||||
private class DropDownRenderer : ToolStripRenderer
|
||||
{
|
||||
this._style = style;
|
||||
private readonly KVisualStyle<COMBOBOXPARTS, State>.Part _style;
|
||||
|
||||
public DropDownRenderer(KVisualStyle<COMBOBOXPARTS, State>.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<COMBOBOXPARTS, State>.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
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user