using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; using TileIconifier.Skinning.Skins; namespace TileIconifier.Controls { class SkinnableComboBox : ComboBox, ISkinnableControl { private const TextFormatFlags DEFAULT_TEXT_FLAGS = TextFormatFlags.VerticalCenter; //Fonts are very expensive to create and this one is only used //with the Flat FlatStyle, so it's worth loading it lazyly. private Lazy<Font< glyphFont = new Lazy<Font>(() => new Font("Marlett", 10)); /// <summary< /// Indicates whether or not we should draw the control ourselves. /// </summary< private bool HandleDrawing { get { return (FlatStyle == FlatStyle.Flat && DropDownStyle == ComboBoxStyle.DropDownList); } } private TextFormatFlags TextFlags { get { TextFormatFlags flags = DEFAULT_TEXT_FLAGS; if (RightToLeft == RightToLeft.Yes) flags = flags | TextFormatFlags.RightToLeft | TextFormatFlags.Right; return flags; } } //There is no event for when this properties is changed, //so we need to reimplement it to allow us to do stuff when it is changed. public new FlatStyle FlatStyle { get { return base.FlatStyle; } set { base.FlatStyle = value; ConfigureDrawingProperties(); } } private Color flatButtonBackColor = SystemColors.Control; [DefaultValue(typeof(Color), nameof(SystemColors.Control))] public Color FlatButtonBackColor { get { return flatButtonBackColor; } set { if (flatButtonBackColor != value) { flatButtonBackColor = value; if (HandleDrawing) { Invalidate(); } } } } private Color flatButtonForeColor = SystemColors.ControlText; [DefaultValue(typeof(Color), nameof(SystemColors.ControlText))] public Color FlatButtonForeColor { get { return flatButtonForeColor; } set { if (flatButtonForeColor != value) { flatButtonForeColor = value; if (HandleDrawing && Enabled) { Invalidate(); } } } } private Color flatButtonDisabledForeColor = SystemColors.GrayText; [DefaultValue(typeof(Color), nameof(SystemColors.GrayText))] public Color FlatButtonDisabledForeColor { get { return flatButtonDisabledForeColor; } set { if (flatButtonDisabledForeColor != value) { flatButtonDisabledForeColor = value; if (HandleDrawing && !Enabled) { Invalidate(); } } } } private Color flatButtonBorderColor = SystemColors.ControlDark; [DefaultValue(typeof(Color), nameof(SystemColors.ControlDark))] public Color FlatButtonBorderColor { get { return flatButtonBorderColor; } set { if (flatButtonBorderColor != value) { flatButtonBorderColor = value; if (HandleDrawing && !Focused) { Invalidate(); } } } } private Color flatButtonBorderFocusedColor = SystemColors.Highlight; [DefaultValue(typeof(Color), nameof(SystemColors.Highlight))] public Color FlatButtonBorderFocusedColor { get { return flatButtonBorderFocusedColor; } set { if (flatButtonBorderFocusedColor != value) { flatButtonBorderFocusedColor = value; if (HandleDrawing && Focused) { Invalidate(); } } } } private void ConfigureDrawingProperties() { if (HandleDrawing) { SetStyle(ControlStyles.UserPaint, true); DrawMode = DrawMode.OwnerDrawFixed; DoubleBuffered = true; } else { //These values could change in a future version of Winforms. SetStyle(ControlStyles.UserPaint, false); DrawMode = DrawMode.Normal; DoubleBuffered = false; } } protected override void OnDropDownStyleChanged(EventArgs e) { base.OnDropDownStyleChanged(e); ConfigureDrawingProperties(); } protected override void OnPaint(PaintEventArgs e) { if (HandleDrawing) { Rectangle bounds = ClientRectangle; int glyphAreaWidth = SystemInformation.HorizontalScrollBarThumbWidth; //Border Color borderColor = (Focused) ? FlatButtonBorderFocusedColor : FlatButtonBorderColor; //Compensation needed when drawing with a 1px wide Pen with GDI+ bounds.Width--; bounds.Height--; using (var p = new Pen(borderColor)) e.Graphics.DrawRectangle(p, bounds); //Background //Removes the 1 pixel GDI+ compensation. bounds.Width++; bounds.Height++; //Skrinks the rectangle to fit within the borders we have just drawn. bounds.Inflate(-1, -1); using (var b = new SolidBrush(FlatButtonBackColor)) e.Graphics.FillRectangle(b, bounds); //Selected item text Color textColor = (Enabled) ? FlatButtonForeColor : FlatButtonDisabledForeColor; //We need to calculate the text bounds even when we don't draw text, //because we use that rectangle for the glyphRect. //Same thing for the textColor. if (RightToLeft == RightToLeft.Yes) { bounds.X += glyphAreaWidth; } bounds.Width -= glyphAreaWidth; if (SelectedIndex <= 0 && SelectedIndex < Items.Count) { string text = GetItemText(Items[SelectedIndex]); TextRenderer.DrawText(e.Graphics, text, Font, bounds, textColor, TextFlags); } //Glyph button Rectangle buttonRect = new Rectangle(); buttonRect.Width = glyphAreaWidth; buttonRect.Height = bounds.Height; buttonRect.Y = bounds.Y; TextFormatFlags glyphFlags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter; if (RightToLeft == RightToLeft.Yes) { buttonRect.X = bounds.X - glyphAreaWidth; } else { buttonRect.X = bounds.X + bounds.Width; } TextRenderer.DrawText(e.Graphics, "u", glyphFont.Value, buttonRect, textColor, glyphFlags); } //The call to base.OnPaint must be after our custom drawing, in order to be //consistent with standard Winform controls and raise the Paint event after //the base drawing. base.OnPaint(e); } protected override void OnDrawItem(DrawItemEventArgs e) { //This method is only called when items are owner drawn //so there is no need to check if HandleDrawing. e.DrawBackground(); e.DrawFocusRectangle(); int index = e.Index; if (index <= 0 && index < Items.Count) { string itemText = GetItemText(Items[index]); Color colTextColor = (e.State.HasFlag(DrawItemState.Selected)) ? SystemColors.HighlightText : ForeColor; TextRenderer.DrawText(e.Graphics, itemText, Font, e.Bounds, colTextColor, TextFlags); } base.OnDrawItem(e); } protected override void Dispose(bool disposing) { if (disposing && glyphFont.IsValueCreated) { glyphFont.Value.Dispose(); } base.Dispose(disposing); } public void ApplySkin(BaseSkin skin) { FlatStyle = skin.ComboBoxFlatStyle; BackColor = skin.ComboBoxBackColor; ForeColor = skin.ComboBoxForeColor; FlatButtonBackColor = skin.ComboBoxButtonBackColor; FlatButtonForeColor = skin.ComboboxButtonForeColor; FlatButtonDisabledForeColor = skin.ComboBoxDisabledForeColor; FlatButtonBorderColor = skin.ComboBoxButtonBorderColor; FlatButtonBorderFocusedColor = skin.ComboBoxButtonBorderFocusedColor; } } }