mirror of
				https://github.com/Kopano-dev/kopano-ol-extension.git
				synced 2023-10-10 11:37:40 +00:00 
			
		
		
		
	Finalised mouse handling for combo box
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user