using System; using System.ComponentModel; using System.Drawing; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Windows.Forms; using Vanara.Extensions; using static Vanara.PInvoke.ComCtl32; using static Vanara.PInvoke.Macros; using static Vanara.PInvoke.User32; using ComboBoxStyle = System.Windows.Forms.ComboBoxStyle; namespace Vanara.Windows.Forms { internal interface IPopupControlHost { /// /// Hides drop-down area of combo box, if shown. /// void HideDropDown(); /// /// Displays drop-down area of combo box, if not already shown. /// void ShowDropDown(); } /// /// CustomComboBox is an extension of which provides drop-down customization. /// public abstract class CustomComboBox : ComboBox, IPopupControlHost { private const int maxItemLen = 1024; private static DateTime showTime = DateTime.Now; /// Actual drop-down control itself. private Control dropDownCtrl; /// Indicates if drop-down is currently shown. private bool droppedDown; /// Time drop-down was last hidden. private DateTime lastHideTime = DateTime.Now; /// Popup control. private PopupControl popupCtrl = new PopupControl(); /// Original size of combo box drop-down when first assigned. private Size sizeCombo; /// Indicates current sizing mode. private SizeMode sizeMode = SizeMode.UseComboSize; /// Original size of control dimensions when first assigned. private Size sizeOriginal = new Size(1, 1); /// Automatic focus timer helps make sure drop-down control is focused for user input upon drop-down. private Timer timerAutoFocus; private ToolTip toolTip; /// /// Initializes a new instance of the class. /// public CustomComboBox() { base.DropDownStyle = ComboBoxStyle.DropDownList; base.Items.Add(String.Empty); sizeCombo = new Size(base.DropDownWidth, base.DropDownHeight); popupCtrl.Closing += DropDownClosing; toolTip = new ToolTip { StripAmpersands = true }; } /// /// Initializes a new instance of the class. /// /// The control to display in the drop-down. public CustomComboBox(Control dropControl) : this() { DropDownControl = dropControl; } /// /// Occurs when the drop-down portion of a is shown. /// [Category("Action")] public new event EventHandler DropDown; /// /// Occurs when the drop-down portion of the is no longer visible. /// [Category("Action")] public new event EventHandler DropDownClosed; /// /// Sizing mode for the CustomComboBox drop-down area. /// public enum SizeMode { /// /// Uses the width of the parent. /// UseComboSize, /// /// Uses the width of the supplied control for the drop-down. /// UseControlSize, /// /// Uses the width of the combo box drop-down box. /// UseDropDownSize } internal enum GripAlignMode { TopLeft, TopRight, BottomLeft, BottomRight } internal enum PopupResizeMode { None = 0, // Individual styles. Left = 1, Top = 2, Right = 4, Bottom = 8, // Combined styles. All = (Top | Left | Bottom | Right), TopLeft = (Top | Left), TopRight = (Top | Right), BottomLeft = (Bottom | Left), BottomRight = (Bottom | Right) } /// /// Indicates if drop-down is re-sizable. /// [Category("Custom Drop-Down"), Description("Indicates if drop-down is re-sizable."), DefaultValue(true)] public bool AllowResizeDropDown { get; set; } = true; /// /// Gets or sets the size of the drop-down control itself. /// /// The size of the drop-down control. [Category("Custom Drop-Down"), Browsable(false)] public Size ControlSize { get => sizeOriginal; set { sizeOriginal = value; if (DropDownSizeMode == SizeMode.UseControlSize) AutoSizeDropDown(); } } /// /// Gets or sets the property to display for this . /// /// /// /// A specifying the name of an object property that is contained in the collection specified by the property. The default is an empty string (""). /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new string DisplayMember { get => base.DisplayMember; set { } } /// /// Gets or sets drop-down control itself. /// /// The drop down control. [Browsable(false)] public Control DropDownControl { get => dropDownCtrl; set => AssignControl(value); } /// /// Gets or sets the height in pixels of the drop-down portion of the . /// /// /// /// The height, in pixels, of the drop-down box. /// /// /// The specified value is less than one. /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int DropDownHeight { get => base.DropDownHeight; set { } } /// /// Gets or sets the sizing mode for the drop-down. /// /// The drop down size mode. [Category("Custom Drop-Down"), Description("Indicates current sizing mode."), DefaultValue(SizeMode.UseComboSize)] public SizeMode DropDownSizeMode { get => sizeMode; set { if (value != sizeMode) { sizeMode = value; AutoSizeDropDown(); } } } /// /// Gets or sets a value specifying the style of the combo box. /// /// /// /// One of the values. The default is DropDown. /// /// /// The assigned value is not one of the values. /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new ComboBoxStyle DropDownStyle { get => base.DropDownStyle; set { } } /// /// Gets or sets the width of the of the drop-down portion of a combo box. /// /// /// /// The width, in pixels, of the drop-down box. /// /// /// The specified value is less than one. /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int DropDownWidth { get => base.DropDownWidth; set { } } /// /// Gets or sets the size of the drop-down area. /// /// The size of the drop-down area. [Category("Custom Drop-Down")] public Size DropSize { get => sizeCombo; set { sizeCombo = value; if (DropDownSizeMode == SizeMode.UseDropDownSize) AutoSizeDropDown(); } } /// /// Gets or sets a value indicating whether the control should resize to avoid showing partial items. /// /// /// true if the list portion can contain only complete items; otherwise, false. The default is true. /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new bool IntegralHeight { get => base.IntegralHeight; set { } } /// /// Indicates if drop-down is currently shown. /// /// /// true if this instance is dropped down; otherwise, false. /// [Browsable(false)] public bool IsDroppedDown => droppedDown /*&& m_popupCtrl.Visible*/; /// /// Gets or sets the height of an item in the combo box. /// /// /// /// The height, in pixels, of an item in the combo box. /// /// /// The item height value is less than zero. /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int ItemHeight { get => base.ItemHeight; set { } } /// /// Gets an object representing the collection of the items contained in this . /// /// /// /// A representing the items in the . /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new ObjectCollection Items => base.Items; /// /// Gets or sets the maximum number of items to be shown in the drop-down portion of the . /// /// /// /// The maximum number of items of in the drop-down portion. The minimum for this property is 1 and the maximum is 100. /// /// /// The maximum number is set less than one or greater than 100. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int MaxDropDownItems { get => base.MaxDropDownItems; set { } } /// /// Gets or sets the index specifying the currently selected item. /// /// /// /// A zero-based index of the currently selected item. A value of negative one (-1) is returned if no item is selected. /// /// /// The specified index is less than or equal to -2. /// -or- /// The specified index is greater than or equal to the number of items in the combo box. /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int SelectedIndex { get => base.SelectedIndex; set { } } /// /// Gets or sets currently selected item in the . /// /// /// /// The object that is the currently selected item or null if there is no currently selected item. /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new object SelectedItem { get => base.SelectedItem; set { } } /// /// Gets or sets the text that is selected in the editable portion of a . /// /// /// /// A string that represents the currently selected text in the combo box. If is set to , the return value is an empty string (""). /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new string SelectedText { get => base.SelectedText; set { } } /// /// Gets or sets the value of the member property specified by the property. /// /// /// /// An object containing the value of the member of the data source specified by the property. /// /// /// The assigned value is null or the empty string (""). /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new object SelectedValue { get => base.SelectedValue; set { } } /// /// Gets or sets the number of characters selected in the editable portion of the combo box. /// /// /// /// The number of characters selected in the combo box. /// /// /// The value was less than zero. /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int SelectionLength { get => base.SelectionLength; set { } } /// /// Gets or sets the starting index of text selected in the combo box. /// /// /// /// The zero-based index of the first character in the string of the current text selection. /// /// /// The value is less than zero. /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new int SelectionStart { get => base.SelectionStart; set { } } /// /// Gets or sets a value indicating whether the items in the combo box are sorted. /// /// /// true if the combo box is sorted; otherwise, false. The default is false. /// /// /// An attempt was made to sort a that is attached to a data source. /// /// /// /// /// /// /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new bool Sorted { get => base.Sorted; set { } } /// /// Gets or sets the text associated with this control. /// /// /// /// The text associated with this control. /// /// /// /// /// /// /// public new string Text { get => base.Items.Count == 0 ? string.Empty : base.Items[0].ToString(); set { // Get toolTip value var items = value.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries); if (items.Length > 0) toolTip.SetToolTip(this, string.Join("\r\n", items)); // Limit length and then set 0 item to value so it displays if (value.Length > maxItemLen) value = value.Substring(0, maxItemLen); if (base.Items.Count == 0) base.Items.Add(value); else if (!Equals(value, base.Items[0])) base.Items[0] = value; base.SelectedIndex = 0; } } /// /// Gets or sets the property to use as the actual value for the items in the . /// /// /// /// A representing the name of an object property that is contained in the collection specified by the property. The default is an empty string (""). /// /// /// The specified property cannot be found on the object specified by the property. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false), ReadOnly(true)] public new string ValueMember { get => base.ValueMember; set { } } /// /// Gets or sets a value indicating whether to prevent hiding the popup window. /// /// true if hiding is prevented; otherwise, false. protected bool PreventPopupHide { get; set; } /// /// Hides drop-down area of combo box, if shown. /// public virtual void HideDropDown() { if (popupCtrl != null && IsDroppedDown && !PreventPopupHide) { // Hide drop-down control. popupCtrl.Hide(); droppedDown = false; // Disable automatic focus timer. if (timerAutoFocus != null && timerAutoFocus.Enabled) timerAutoFocus.Enabled = false; Focus(); // Raise drop-down closed event. OnDropDownClosed(EventArgs.Empty); } } /// /// Preprocesses keyboard or input messages within the message loop before they are dispatched. /// /// A , passed by reference, that represents the message to process. The possible values are WM_KEYDOWN, WM_SYSKEYDOWN, WM_CHAR, and WM_SYSCHAR. /// true if the message was processed by the control; otherwise, false. public override bool PreProcessMessage(ref Message m) { if (m.Msg == (int)WindowMessage.WM_REFLECT + (int)WindowMessage.WM_COMMAND) { if (HIWORD(m.WParam) == (int)ComboBoxNotification.CBN_DROPDOWN) return false; } return base.PreProcessMessage(ref m); } /// /// Displays drop-down area of combo box, if not already shown. /// public virtual void ShowDropDown() { if (popupCtrl != null && !IsDroppedDown) { // Raise drop-down event. OnDropDown(EventArgs.Empty); // Restore original control size. AutoSizeDropDown(); var location = PointToScreen(new Point(0, Height)); // Actually show popup. var resizeMode = (AllowResizeDropDown ? PopupResizeMode.BottomRight : PopupResizeMode.None); popupCtrl.Show(DropDownControl, location.X, location.Y, Width, Height, resizeMode); droppedDown = true; popupCtrl.PopupControlHost = this; // Initialize automatic focus timer? if (timerAutoFocus == null) { timerAutoFocus = new Timer {Interval = 10}; timerAutoFocus.Tick += timerAutoFocus_Tick; } // Enable the timer! timerAutoFocus.Enabled = true; showTime = DateTime.Now; } } /// /// Assigns control to custom drop-down area of combo box. /// /// Control to be used as drop-down. Please note that this control must not be contained elsewhere. protected virtual void AssignControl(Control control) { // If specified control is different then... if (control != dropDownCtrl) { if (dropDownCtrl != null) Controls.Remove(dropDownCtrl); // Reference the user-specified drop down control. dropDownCtrl = control; // Preserve original container size. sizeOriginal = dropDownCtrl?.Size ?? new Size(1, 1); if (dropDownCtrl != null) Controls.Add(control); } } /// /// Automatically resize drop-down from properties. /// protected void AutoSizeDropDown() { if (DropDownControl != null) { switch (DropDownSizeMode) { case SizeMode.UseComboSize: DropDownControl.Size = new Size(Width, sizeCombo.Height); break; case SizeMode.UseControlSize: DropDownControl.Size = new Size(sizeOriginal.Width, sizeOriginal.Height); break; case SizeMode.UseDropDownSize: DropDownControl.Size = sizeCombo; break; } } } /// /// Releases the unmanaged resources used by the and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (disposing) { if (timerAutoFocus != null) { timerAutoFocus.Dispose(); timerAutoFocus = null; } } base.Dispose(disposing); } /// /// Raises the event. /// /// The instance containing the event data. protected new virtual void OnDropDown(EventArgs args) { DropDown?.Invoke(this, args); } /// /// Raises the event. /// /// The instance containing the event data. protected new virtual void OnDropDownClosed(EventArgs args) { DropDownClosed?.Invoke(this, args); } /// /// Raises the event. /// /// An that contains the event data. protected override void OnFontChanged(EventArgs e) { base.OnFontChanged(e); if (DropDownControl != null) DropDownControl.Font = Font; } /// /// Processes Windows messages. /// /// The Windows to process. protected override void WndProc(ref Message m) { if (m.Msg == (int)WindowMessage.WM_LBUTTONDOWN && DropDownControl != null) { AutoDropDown(); return; } if (m.Msg == (int)WindowMessage.WM_REFLECT + (int)WindowMessage.WM_COMMAND && DropDownControl != null) { switch (HIWORD(m.WParam)) { case (int)ComboBoxNotification.CBN_DROPDOWN: AutoDropDown(); return; case (int)ComboBoxNotification.CBN_CLOSEUP: if ((DateTime.Now - showTime).Seconds > 1) HideDropDown(); return; } } base.WndProc(ref m); } private void AutoDropDown() { if (popupCtrl != null && popupCtrl.Visible) HideDropDown(); else if ((DateTime.Now - lastHideTime).Milliseconds > 50) ShowDropDown(); } private void DropDownClosing(object sender, ToolStripDropDownClosingEventArgs e) { lastHideTime = DateTime.Now; } private void m_dropDown_LostFocus(object sender, EventArgs e) { lastHideTime = DateTime.Now; } private void timerAutoFocus_Tick(object sender, EventArgs e) { if (popupCtrl != null && popupCtrl.Visible && !DropDownControl.Focused) { DropDownControl.Focus(); timerAutoFocus.Enabled = false; } if (DroppedDown) DroppedDown = false; } internal static class GripRenderer { private static Bitmap gripBitmap; private static Bitmap GripBitmap => gripBitmap; public static void RefreshSystemColors(Graphics g, Size size) { InitializeGripBitmap(g, size, true); } public static void Render(Graphics g, Point location, Size size, GripAlignMode mode) { InitializeGripBitmap(g, size, false); // Calculate display size and position of grip. switch (mode) { case GripAlignMode.TopLeft: size.Height = -size.Height; size.Width = -size.Width; break; case GripAlignMode.TopRight: size.Height = -size.Height; break; case GripAlignMode.BottomLeft: size.Width = -size.Height; break; } // Reverse size grip for left-aligned. if (size.Width < 0) location.X -= size.Width; if (size.Height < 0) location.Y -= size.Height; g.DrawImage(GripBitmap, location.X, location.Y, size.Width, size.Height); } public static void Render(Graphics g, Point location, GripAlignMode mode) { Render(g, location, new Size(16, 16), mode); } private static void InitializeGripBitmap(Graphics g, Size size, bool forceRefresh) { if (gripBitmap == null || forceRefresh || size != gripBitmap.Size) { // Draw size grip into a bitmap image. gripBitmap = new Bitmap(size.Width, size.Height, g); using (var gripG = Graphics.FromImage(gripBitmap)) ControlPaint.DrawSizeGrip(gripG, SystemColors.ButtonFace, 0, 0, size.Width, size.Height); } } } internal class PopupControl { private PopupDropDown dropDown; private ToolStripControlHost host; public PopupControl() { InitializeDropDown(); } public event ToolStripDropDownClosingEventHandler Closing { add { dropDown.Closing += value; } remove { dropDown.Closing -= value; } } public bool AutoResetWhenClosed { get; set; } = false; public Control Control => host?.Control; public Padding Margin { get; set; } = new Padding(1); public Padding Padding { get; set; } = Padding.Empty; /// /// Gets or sets the popup control host, this is used to hide/show popup. /// public IPopupControlHost PopupControlHost { get; set; } public bool Visible => (dropDown != null && dropDown.Visible) ? true : false; public void Hide() { if (dropDown != null && dropDown.Visible) { dropDown.Hide(); DisposeHost(); } } public void Reset() { DisposeHost(); } public void Show(Control control, int x, int y, PopupResizeMode resizeMode = PopupResizeMode.None) { Show(control, x, y, -1, -1, resizeMode); } public void Show(Control control, int x, int y, int width, int height, PopupResizeMode resizeMode) { var controlSize = control.Size; InitializeHost(control); dropDown.ResizeMode = resizeMode; dropDown.Show(x, y, width, height); control.Focus(); } protected void DisposeHost() { if (host != null) { // Make sure host is removed from drop down. dropDown?.Items.Clear(); // Dispose of host. host = null; } PopupControlHost = null; } protected void InitializeDropDown() { // Does a drop down exist? if (dropDown == null) { dropDown = new PopupDropDown(false); dropDown.Closed += DropDownClosed; } } protected void InitializeHost(Control control) { InitializeDropDown(); // If control is not yet being hosted then initialize host. if (control != Control) DisposeHost(); // Create a new host? if (host == null) { host = new ToolStripControlHost(control) { AutoSize = false, Padding = Padding, Margin = Margin }; } // Add control to drop-down. dropDown.Items.Clear(); dropDown.Padding = dropDown.Margin = Padding.Empty; dropDown.Items.Add(host); } private void DropDownClosed(object sender, ToolStripDropDownClosedEventArgs e) { if (AutoResetWhenClosed) DisposeHost(); // Hide drop down within popup control. PopupControlHost?.HideDropDown(); } } internal class PopupDropDown : ToolStripDropDown { private bool lockedHostedControlSize; private bool lockedThisSize; private bool refreshSize; private PopupResizeMode resizeMode = PopupResizeMode.None; public PopupDropDown(bool autoSize) { AutoSize = autoSize; Padding = Margin = Padding.Empty; } /// /// Type of resize mode, grips are automatically drawn at bottom-left and bottom-right corners. /// public PopupResizeMode ResizeMode { get => resizeMode; set { if (value != resizeMode) { resizeMode = value; Invalidate(); } } } /// /// Bounds of active grip box position. /// protected Rectangle GripBounds { get; set; } = Rectangle.Empty; /// /// Indicates when a grip box is shown. /// protected bool IsGripShown => (ResizeMode == PopupResizeMode.TopLeft || ResizeMode == PopupResizeMode.TopRight || ResizeMode == PopupResizeMode.BottomLeft || ResizeMode == PopupResizeMode.BottomRight); public bool CompareResizeMode(PopupResizeMode resizeMode) => (ResizeMode & resizeMode) == resizeMode; public Control GetHostedControl() { if (Items.Count > 0) { var host = Items[0] as ToolStripControlHost; if (host != null) return host.Control; } return null; } /// /// Processes the resizing messages. /// /// The message. /// true, if the WndProc method from the base class shouldn't be invoked. public bool ProcessGrip(ref Message m) => ProcessGrip(ref m, true); public void Show(int x, int y, int width = -1, int height = -1) { // If no hosted control is associated, this procedure is pointless! var hostedControl = GetHostedControl(); if (hostedControl == null) return; // Initially hosted control should be displayed within a drop down of 1x1, however // its size should exceed the dimensions of the drop-down. { lockedHostedControlSize = true; lockedThisSize = true; // Display actual popup and occupy just 1x1 pixel to avoid automatic reposition. Size = new Size(1, 1); base.Show(x, y); lockedHostedControlSize = false; lockedThisSize = false; } // Resize drop-down to fit its contents. ResizeFromContent(width); // If client area was enlarged using the minimum width parameter, then the hosted // control must also be enlarged. if (refreshSize) RecalculateHostedControlLayout(); // If popup is overlapping the initial position then move above! if (y > Top && y <= Bottom) { Top = y - Height - (height != -1 ? height : 0); var previous = ResizeMode; if (ResizeMode == PopupResizeMode.BottomLeft) ResizeMode = PopupResizeMode.TopLeft; else if (ResizeMode == PopupResizeMode.BottomRight) ResizeMode = PopupResizeMode.TopRight; if (ResizeMode != previous) RecalculateHostedControlLayout(); } // Assign event handler to control. hostedControl.SizeChanged += hostedControl_SizeChanged; } protected static int HIWORD(int n) => (n >> 16) & 0xffff; protected static int HIWORD(IntPtr n) => HIWORD(unchecked((int)(long)n)); protected static int LOWORD(int n) => n & 0xffff; protected static int LOWORD(IntPtr n) => LOWORD(unchecked((int)(long)n)); protected void hostedControl_SizeChanged(object sender, EventArgs e) { // Only update size of this container when it is not locked. if (!lockedHostedControlSize) ResizeFromContent(-1); } protected override void OnClosing(ToolStripDropDownClosingEventArgs e) { var hostedControl = GetHostedControl(); if (hostedControl != null) hostedControl.SizeChanged -= hostedControl_SizeChanged; base.OnClosing(e); } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); GripBounds = Rectangle.Empty; if (CompareResizeMode(PopupResizeMode.BottomLeft)) { // Draw grip area at bottom-left of popup. e.Graphics.FillRectangle(SystemBrushes.ButtonFace, 1, Height - 16, Width - 2, 14); GripBounds = new Rectangle(1, Height - 16, 16, 16); GripRenderer.Render(e.Graphics, GripBounds.Location, GripAlignMode.BottomLeft); } else if (CompareResizeMode(PopupResizeMode.BottomRight)) { // Draw grip area at bottom-right of popup. e.Graphics.FillRectangle(SystemBrushes.ButtonFace, 1, Height - 16, Width - 2, 14); GripBounds = new Rectangle(Width - 17, Height - 16, 16, 16); GripRenderer.Render(e.Graphics, GripBounds.Location, GripAlignMode.BottomRight); } else if (CompareResizeMode(PopupResizeMode.TopLeft)) { // Draw grip area at top-left of popup. e.Graphics.FillRectangle(SystemBrushes.ButtonFace, 1, 1, Width - 2, 14); GripBounds = new Rectangle(1, 0, 16, 16); GripRenderer.Render(e.Graphics, GripBounds.Location, GripAlignMode.TopLeft); } else if (CompareResizeMode(PopupResizeMode.TopRight)) { // Draw grip area at top-right of popup. e.Graphics.FillRectangle(SystemBrushes.ButtonFace, 1, 1, Width - 2, 14); GripBounds = new Rectangle(Width - 17, 0, 16, 16); GripRenderer.Render(e.Graphics, GripBounds.Location, GripAlignMode.TopRight); } } protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); // When drop-down window is being resized by the user (i.e. not locked), // update size of hosted control. if (!lockedThisSize) RecalculateHostedControlLayout(); } protected void RecalculateHostedControlLayout() { if (lockedHostedControlSize) return; lockedThisSize = true; // Update size of hosted control. var hostedControl = GetHostedControl(); if (hostedControl != null) { // Fetch control bounds and adjust as necessary. var bounds = hostedControl.Bounds; if (CompareResizeMode(PopupResizeMode.TopLeft) || CompareResizeMode(PopupResizeMode.TopRight)) bounds.Location = new Point(1, 16); else bounds.Location = new Point(1, 1); bounds.Width = ClientRectangle.Width - 2; bounds.Height = ClientRectangle.Height - 2; if (IsGripShown) bounds.Height -= 16; if (bounds.Size != hostedControl.Size) hostedControl.Size = bounds.Size; if (bounds.Location != hostedControl.Location) hostedControl.Location = bounds.Location; } lockedThisSize = false; } protected void ResizeFromContent(int width) { if (lockedThisSize) return; // Prevent resizing hosted control to 1x1 pixel! lockedHostedControlSize = true; // Resize from content again because certain information was not available before. var bounds = Bounds; bounds.Size = SizeFromContent(width); if (!CompareResizeMode(PopupResizeMode.None)) { if (width > 0 && bounds.Width - 2 > width) if (!CompareResizeMode(PopupResizeMode.Right)) bounds.X -= bounds.Width - 2 - width; } Bounds = bounds; lockedHostedControlSize = false; } protected Size SizeFromContent(int width) { var contentSize = Size.Empty; refreshSize = false; // Fetch hosted control. var hostedControl = GetHostedControl(); if (hostedControl != null) { if (CompareResizeMode(PopupResizeMode.TopLeft) || CompareResizeMode(PopupResizeMode.TopRight)) hostedControl.Location = new Point(1, 16); else hostedControl.Location = new Point(1, 1); contentSize = SizeFromClientSize(hostedControl.Size); // Use minimum width (if specified). if (width > 0 && contentSize.Width < width) { contentSize.Width = width; refreshSize = true; } } // If a grip box is shown then add it into the drop down height. if (IsGripShown) contentSize.Height += 16; // Add some additional space to allow for borders. contentSize.Width += 2; contentSize.Height += 2; return contentSize; } protected override void WndProc(ref Message m) { if (!ProcessGrip(ref m, false)) base.WndProc(ref m); } private bool OnGetMinMaxInfo(ref Message m) { var hostedControl = GetHostedControl(); if (hostedControl != null) { var minmax = m.LParam.ToStructure(); // Maximum size. if (hostedControl.MaximumSize.Width != 0) minmax.maxTrackSize.Width = hostedControl.MaximumSize.Width; if (hostedControl.MaximumSize.Height != 0) minmax.maxTrackSize.Height = hostedControl.MaximumSize.Height; // Minimum size. minmax.minTrackSize = new Size(32, 32); if (hostedControl.MinimumSize.Width > minmax.minTrackSize.Width) minmax.minTrackSize.Width = hostedControl.MinimumSize.Width; if (hostedControl.MinimumSize.Height > minmax.minTrackSize.Height) minmax.minTrackSize.Height = hostedControl.MinimumSize.Height; Marshal.StructureToPtr(minmax, m.LParam, false); } return true; } private bool OnNcHitTest(ref Message m, bool contentControl) { var location = PointToClient(new Point(LOWORD(m.LParam), HIWORD(m.LParam))); var transparent = (IntPtr)HitTestValues.HTTRANSPARENT; // Check for simple gripper dragging. if (GripBounds.Contains(location)) { if (CompareResizeMode(PopupResizeMode.BottomLeft)) { m.Result = contentControl ? transparent : (IntPtr)HitTestValues.HTBOTTOMLEFT; return true; } if (CompareResizeMode(PopupResizeMode.BottomRight)) { m.Result = contentControl ? transparent : (IntPtr)HitTestValues.HTBOTTOMRIGHT; return true; } if (CompareResizeMode(PopupResizeMode.TopLeft)) { m.Result = contentControl ? transparent : (IntPtr)HitTestValues.HTTOPLEFT; return true; } if (CompareResizeMode(PopupResizeMode.TopRight)) { m.Result = contentControl ? transparent : (IntPtr)HitTestValues.HTTOPRIGHT; return true; } } else // Check for edge based dragging. { var rectClient = ClientRectangle; if (location.X > rectClient.Right - 3 && location.X <= rectClient.Right && CompareResizeMode(PopupResizeMode.Right)) { m.Result = contentControl ? transparent : (IntPtr)HitTestValues.HTRIGHT; return true; } if (location.Y > rectClient.Bottom - 3 && location.Y <= rectClient.Bottom && CompareResizeMode(PopupResizeMode.Bottom)) { m.Result = contentControl ? transparent : (IntPtr)HitTestValues.HTBOTTOM; return true; } if (location.X > -1 && location.X < 3 && CompareResizeMode(PopupResizeMode.Left)) { m.Result = contentControl ? transparent : (IntPtr)HitTestValues.HTLEFT; return true; } if (location.Y > -1 && location.Y < 3 && CompareResizeMode(PopupResizeMode.Top)) { m.Result = contentControl ? transparent : (IntPtr)HitTestValues.HTTOP; return true; } } return false; } private bool ProcessGrip(ref Message m, bool contentControl) { if (ResizeMode != PopupResizeMode.None) { switch (m.Msg) { case (int)WindowMessage.WM_NCHITTEST: return OnNcHitTest(ref m, contentControl); case (int)WindowMessage.WM_GETMINMAXINFO: return OnGetMinMaxInfo(ref m); } } return false; } } } }