using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; using Vanara.Drawing; using Vanara.Extensions; using static Vanara.PInvoke.ComCtl32; using static Vanara.PInvoke.UxTheme; using static Vanara.PInvoke.Macros; using static Vanara.PInvoke.User32; using ComboBoxStyle = System.Windows.Forms.ComboBoxStyle; namespace Vanara.Windows.Forms { /// Interface that exposes an Enabled property for an item supplied to . public interface IEnableable { /// Gets a value indicating whether an item is enabled. /// true if enabled; otherwise, false. bool Enabled { get; } } /// A version of that allows for disabled items. [ToolboxBitmap(typeof(DisabledItemComboBox), "Control")] public class DisabledItemComboBox : ComboBox { private const TextFormatFlags tff = TextFormatFlags.Default | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine | TextFormatFlags.NoPadding; private bool animationsNeedCleanup; private ComboBoxState currentState = ComboBoxState.Normal, newState = ComboBoxState.Normal; private ListBoxNativeWindow dropDownWindow; private readonly VisualStyleRenderer vsr; /// Initializes a new instance of the class. public DisabledItemComboBox() { SetStyle(/*ControlStyles.Opaque |*/ ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true); DrawMode = DrawMode.OwnerDrawFixed; DropDownStyle = ComboBoxStyle.DropDownList; if (Environment.OSVersion.Version.Major >= 6 && VisualStyleRenderer.IsSupported && Application.RenderWithVisualStyles) vsr = new VisualStyleRenderer("COMBOBOX", 5, 0); } /// /// Gets or sets a value indicating whether your code or the operating system will handle drawing of elements in the list. /// /// One of the enumeration values. The default is . /// [DefaultValue(DrawMode.OwnerDrawFixed), Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public new DrawMode DrawMode { get => base.DrawMode; set => base.DrawMode = value; } /// Gets or sets a value specifying the style of the combo box. /// One of the values. The default is DropDown. /// [DefaultValue(ComboBoxStyle.DropDownList), Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public new ComboBoxStyle DropDownStyle { get => base.DropDownStyle; set => base.DropDownStyle = value; } /// Gets or sets the state of the combobox. /// The state. private ComboBoxState State { get => currentState; set { var diff = !Equals(currentState, value); newState = value; if (diff) { if (animationsNeedCleanup && IsHandleCreated && !IsDisposed) BufferedPaintStopAllAnimations(Handle); Invalidate(); } } } /// Determines whether an item is enabled. /// The index of the item. /// true if enabled; otherwise, false. public bool IsItemEnabled(int idx) => !(idx > -1 && idx < Items.Count && Items[idx] is IEnableable && !((IEnableable)Items[idx]).Enabled); /// /// 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 (animationsNeedCleanup && !IsDisposed) { BufferedPaintUnInit(); animationsNeedCleanup = false; } base.Dispose(disposing); } /// Raises the event. /// A that contains the event data. protected override void OnDrawItem(DrawItemEventArgs e) { var itemString = e.Index >= 0 ? GetItemText(Items[e.Index]) : string.Empty; if ((e.State & DrawItemState.ComboBoxEdit) != DrawItemState.ComboBoxEdit) { if (e.Index >= 0) { var iEnabled = IsItemEnabled(e.Index); if (iEnabled) { e.DrawBackground(); e.DrawFocusRectangle(); } else { using (var bb = new SolidBrush(e.BackColor)) e.Graphics.FillRectangle(bb, e.Bounds); } TextRenderer.DrawText(e.Graphics, itemString, e.Font, Rectangle.Inflate(e.Bounds, -2, 0), iEnabled ? e.ForeColor : SystemColors.GrayText, tff); } } base.OnDrawItem(e); } /// Raises the event. /// An that contains the event data. protected override void OnDropDown(EventArgs e) { base.OnDropDown(e); State = ComboBoxState.Pressed; } /// Raises the event. /// An that contains the event data. protected override void OnDropDownClosed(EventArgs e) { base.OnDropDownClosed(e); State = ComboBoxState.Normal; } /// Raises the event. /// An that contains the event data. protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); if (vsr != null) { BufferedPaintInit(); animationsNeedCleanup = true; } } /// Raises the event. /// An that contains the event data. protected override void OnHandleDestroyed(EventArgs e) { dropDownWindow?.ReleaseHandle(); dropDownWindow = null; base.OnHandleDestroyed(e); } /// Raises the event. /// A that contains the event data. protected override void OnKeyPress(KeyPressEventArgs e) { var idx = FindEnabledString(e.KeyChar.ToString(), SelectedIndex); if (idx == -1 || idx == SelectedIndex) e.Handled = true; base.OnKeyPress(e); } /// Raises the event. /// An that contains the event data. protected override void OnLostFocus(EventArgs e) { base.OnLostFocus(e); Invalidate(); } /// Raises the event. /// A that contains the event data. protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); State = ComboBoxState.Pressed; } /// Raises the event. /// An that contains the event data. protected override void OnMouseEnter(EventArgs e) { base.OnMouseEnter(e); State = ComboBoxState.Hot; } /// Raises the event. /// An that contains the event data. protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); if (State != ComboBoxState.Pressed) State = ComboBoxState.Normal; } /// Raises the event. /// A that contains the event data. protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (State != ComboBoxState.Pressed) State = ComboBoxState.Hot; } /// Raises the event. /// A that contains the event data. protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (IsDisposed) return; BufferedPaint.PaintAnimation(e.Graphics, this, ClientRectangle, PaintControl, currentState, Enabled ? newState : ComboBoxState.Disabled, (a, b) => (int)vsr.GetTransitionDuration((int)a, (int)b)); } /// Paints the background of the control. /// A that contains information about the control to paint. protected override void OnPaintBackground(PaintEventArgs pevent) { // don't paint the control's background } /// Paints the control. /// The dc. /// The bounds. /// State of the current. /// The data. protected virtual void PaintControl(Graphics dc, Rectangle bounds, ComboBoxState state, int data) { var cbi = COMBOBOXINFO.FromHandle(Handle); var itemText = SelectedIndex >= 0 ? GetItemText(SelectedItem) : string.Empty; Rectangle tr = cbi.rcItem; /*Rectangle tr = bounds; tr.Width -= (SystemInformation.VerticalScrollBarWidth + 2); tr.Inflate(0, -2); tr.Offset(1, 0);*/ Rectangle br = cbi.rcButton; var vsSuccess = false; if (vsr != null) { /*Rectangle r = Rectangle.Inflate(bounds, 1, 1); if (this.DropDownStyle != ComboBoxStyle.DropDownList) { dc.Clear(this.BackColor); ComboBoxRenderer.DrawTextBox(dc, r, itemText, this.Font, tr, tff, state); ComboBoxRenderer.DrawDropDownButton(dc, br, state); } else*/ { try { vsr.DrawParentBackground(dc, bounds, this); vsr.DrawBackground(dc, bounds); if (DropDownStyle != ComboBoxStyle.DropDownList) br.Inflate(1, 1); var cr = DropDownStyle == ComboBoxStyle.DropDownList ? Rectangle.Inflate(br, -1, -1) : br; vsr.SetState((int)(br.Contains(PointToClient(Cursor.Position)) ? state : ComboBoxState.Normal)); vsr.DrawBackground(dc, br, cr); if (Focused && State != ComboBoxState.Pressed) { var sz = TextRenderer.MeasureText(dc, "Wg", Font, tr.Size, TextFormatFlags.Default); var fr = Rectangle.Inflate(tr, 0, (sz.Height - tr.Height) / 2 + 1); ControlPaint.DrawFocusRectangle(dc, fr); } TextRenderer.DrawText(dc, itemText, Font, tr, ForeColor, tff); vsSuccess = true; } catch { } } } if (!vsSuccess) { System.Diagnostics.Debug.WriteLine($"CR:{bounds};Foc:{Focused};St:{state};Tx:{itemText}"); var bgc = Enabled ? BackColor : SystemColors.Control; dc.Clear(bgc); ControlPaint.DrawBorder3D(dc, bounds, Border3DStyle.Sunken); ControlPaint.DrawComboButton(dc, br, Enabled ? (state == ComboBoxState.Pressed ? ButtonState.Pushed : ButtonState.Normal) : ButtonState.Inactive); //using (var bb = new SolidBrush(this.BackColor)) // dc.FillRectangle(bb, tr); if (Focused) { var sz = TextRenderer.MeasureText(dc, "Wg", Font, tr.Size, TextFormatFlags.Default); var fr = Rectangle.Inflate(tr, 0, (sz.Height - tr.Height) / 2 + 1); dc.FillRectangle(SystemBrushes.Highlight, fr); ControlPaint.DrawFocusRectangle(dc, fr); //, this.ForeColor, SystemColors.Highlight); } TextRenderer.DrawText(dc, itemText, Font, tr, Focused ? SystemColors.HighlightText : (Enabled ? ForeColor : SystemColors.GrayText), bgc, tff); } } /// Processes a command key. /// /// A , passed by reference, that represents the window message to process. /// /// One of the values that represents the key to process. /// true if the character was processed by the control; otherwise, false. protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { var visItems = DropDownHeight / ItemHeight; switch (keyData) { case Keys.Down: case Keys.Right: SelectedIndex = GetNextEnabledItemIndex(SelectedIndex, true); return true; case Keys.Up: case Keys.Left: SelectedIndex = GetNextEnabledItemIndex(SelectedIndex, false); return true; case Keys.PageDown: if (SelectedIndex + visItems > Items.Count) SelectedIndex = GetNextEnabledItemIndex(Items.Count, false); else SelectedIndex = GetNextEnabledItemIndex(SelectedIndex + visItems, true); return true; case Keys.PageUp: if (SelectedIndex - visItems < 0) SelectedIndex = GetNextEnabledItemIndex(-1, true); else SelectedIndex = GetNextEnabledItemIndex(SelectedIndex - visItems, false); return true; case Keys.Home: SelectedIndex = GetNextEnabledItemIndex(-1, true); return true; case Keys.End: SelectedIndex = GetNextEnabledItemIndex(Items.Count, false); return true; case Keys.Enter: var pt = dropDownWindow?.MapPointToClient(Cursor.Position) ?? default; var idx = dropDownWindow?.IndexFromPoint(pt.X, pt.Y) ?? default; if (idx >= 0 && IsItemEnabled(idx)) return false; DroppedDown = false; return true; case Keys.Escape: DroppedDown = false; return true; } return base.ProcessCmdKey(ref msg, keyData); } /// Processes Windows messages. /// The Windows to process. protected override void WndProc(ref Message m) { base.WndProc(ref m); if ((int)(long)m.WParam == 0x3e80001 && !IsDisposed) { dropDownWindow = new ListBoxNativeWindow(m.LParam, this); } } private int FindEnabledString(string str, int startIndex) { if (str != null) { if (startIndex < -1 || startIndex >= Items.Count) return -1; var length = str.Length; var num2 = 0; for (var i = (startIndex + 1) % Items.Count; num2 < Items.Count; i = (i + 1) % Items.Count) { num2++; if (IsItemEnabled(i) && string.Compare(str, 0, GetItemText(Items[i]), 0, length, true, System.Globalization.CultureInfo.CurrentUICulture) == 0) return i; } } return -1; } private int GetNextEnabledItemIndex(int startIndex, bool forward = true) { if (forward) { for (var i = startIndex + 1; i < Items.Count; i++) { if (IsItemEnabled(i)) return i; } return startIndex; } else { for (var i = startIndex - 1; i >= 0; i--) { if (IsItemEnabled(i)) return i; } return startIndex; } } private class ListBoxNativeWindow : NativeWindow { private readonly DisabledItemComboBox Parent; public ListBoxNativeWindow(IntPtr handle, DisabledItemComboBox parent) { Parent = parent; AssignHandle(handle); } public int IndexFromPoint(int x, int y) { var n = SendMessage(Handle, 0x1a9u /* LB_ITEMFROMPOINT */, IntPtr.Zero, MAKELPARAM((ushort)x, (ushort)y)); if (HIWORD(n) == 0) return LOWORD(n); return -1; } protected override void WndProc(ref Message m) { if (!Parent.IsDisposed && m.Msg == 0x0202 || m.Msg == 0x0201 || m.Msg == 0x0203) /* WM_LBUTTONUP or WM_LBUTTONDOWN or WM_LBUTTONDBLCLK */ { var idx = IndexFromPoint(SignedLOWORD(m.LParam), SignedHIWORD(m.LParam)); if (idx >= 0 && !Parent.IsItemEnabled(idx)) return; } base.WndProc(ref m); } } } }