diff --git a/DarkUI/Controls/DarkDropdownItem.cs b/DarkUI/Controls/DarkDropdownItem.cs new file mode 100644 index 0000000..e110c29 --- /dev/null +++ b/DarkUI/Controls/DarkDropdownItem.cs @@ -0,0 +1,34 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace DarkUI.Controls +{ + public class DarkDropdownItem + { + #region Property Region + + public string Text { get; set; } + + public Bitmap Icon { get; set; } + + #endregion + + #region Constructor Region + + public DarkDropdownItem() + { } + + public DarkDropdownItem(string text) + { + Text = text; + } + + public DarkDropdownItem(string text, Bitmap icon) + : this(text) + { + Icon = icon; + } + + #endregion + } +} diff --git a/DarkUI/Controls/DarkDropdownList.cs b/DarkUI/Controls/DarkDropdownList.cs new file mode 100644 index 0000000..ad43722 --- /dev/null +++ b/DarkUI/Controls/DarkDropdownList.cs @@ -0,0 +1,489 @@ +using DarkUI.Config; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; + +namespace DarkUI.Controls +{ + public class DarkDropdownList : Control + { + #region Event Region + + public event EventHandler SelectedItemChanged; + + #endregion + + #region Field Region + + private DarkControlState _controlState = DarkControlState.Normal; + + private ObservableCollection _items = new ObservableCollection(); + private DarkDropdownItem _selectedItem; + + private DarkContextMenu _menu = new DarkContextMenu(); + private bool _menuOpen = false; + + private bool _showBorder = true; + + private int _itemHeight = 22; + private int _maxHeight = 130; + + private readonly int _iconSize = 16; + + private ToolStripDropDownDirection _dropdownDirection = ToolStripDropDownDirection.Default; + + #endregion + + #region Property Region + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ObservableCollection Items + { + get { return _items; } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public DarkDropdownItem SelectedItem + { + get { return _selectedItem; } + set + { + _selectedItem = value; + SelectedItemChanged?.Invoke(this, new EventArgs()); + } + } + + [Category("Appearance")] + [Description("Determines whether a border is drawn around the control.")] + [DefaultValue(true)] + public bool ShowBorder + { + get { return _showBorder; } + set + { + _showBorder = value; + Invalidate(); + } + } + + protected override Size DefaultSize + { + get { return new Size(100, 26); } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public DarkControlState ControlState + { + get { return _controlState; } + } + + [Category("Appearance")] + [Description("Determines the height of the individual list view items.")] + [DefaultValue(22)] + public int ItemHeight + { + get { return _itemHeight; } + set + { + _itemHeight = value; + ResizeMenu(); + } + } + + [Category("Appearance")] + [Description("Determines the maximum height of the dropdown panel.")] + [DefaultValue(130)] + public int MaxHeight + { + get { return _maxHeight; } + set + { + _maxHeight = value; + ResizeMenu(); + } + } + + [Category("Behavior")] + [Description("Determines what location the dropdown list appears.")] + [DefaultValue(ToolStripDropDownDirection.Default)] + public ToolStripDropDownDirection DropdownDirection + { + get { return _dropdownDirection; } + set { _dropdownDirection = value; } + } + + #endregion + + #region Constructor Region + + public DarkDropdownList() + { + SetStyle(ControlStyles.OptimizedDoubleBuffer | + ControlStyles.ResizeRedraw | + ControlStyles.UserPaint | + ControlStyles.Selectable | + ControlStyles.UserMouse, true); + + _menu.AutoSize = false; + _menu.Closed += Menu_Closed; + + Items.CollectionChanged += Items_CollectionChanged; + SelectedItemChanged += DarkDropdownList_SelectedItemChanged; + + SetControlState(DarkControlState.Normal); + } + + #endregion + + #region Method Region + + private ToolStripMenuItem GetMenuItem(DarkDropdownItem item) + { + foreach (ToolStripMenuItem menuItem in _menu.Items) + { + if ((DarkDropdownItem)menuItem.Tag == item) + return menuItem; + } + + return null; + } + + private void SetControlState(DarkControlState controlState) + { + if (_menuOpen) + return; + + if (_controlState != controlState) + { + _controlState = controlState; + Invalidate(); + } + } + + private void ShowMenu() + { + if (_menu.Visible) + return; + + SetControlState(DarkControlState.Pressed); + + _menuOpen = true; + + var pos = new Point(0, ClientRectangle.Bottom); + + if (_dropdownDirection == ToolStripDropDownDirection.AboveLeft || _dropdownDirection == ToolStripDropDownDirection.AboveRight) + pos.Y = 0; + + _menu.Show(this, pos, _dropdownDirection); + + if (SelectedItem != null) + { + var selectedItem = GetMenuItem(SelectedItem); + selectedItem.Select(); + } + } + + private void ResizeMenu() + { + var width = ClientRectangle.Width; + var height = (_menu.Items.Count * _itemHeight) + 4; + + if (height > _maxHeight) + height = _maxHeight; + + // Dirty: Check what the autosized items are + foreach (ToolStripMenuItem item in _menu.Items) + { + item.AutoSize = true; + + if (item.Size.Width > width) + width = item.Size.Width; + + item.AutoSize = false; + } + + // Force the size + foreach (ToolStripMenuItem item in _menu.Items) + item.Size = new Size(width - 1, _itemHeight); + + _menu.Size = new Size(width, height); + } + + #endregion + + #region Event Handler Region + + private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Add) + { + foreach (DarkDropdownItem item in e.NewItems) + { + var menuItem = new ToolStripMenuItem(item.Text) + { + Image = item.Icon, + AutoSize = false, + Height = _itemHeight, + Font = Font, + Tag = item, + TextAlign = ContentAlignment.MiddleLeft + }; + + _menu.Items.Add(menuItem); + menuItem.Click += Item_Select; + + if (SelectedItem == null) + SelectedItem = item; + } + } + + if (e.Action == NotifyCollectionChangedAction.Remove) + { + foreach (DarkDropdownItem item in e.OldItems) + { + foreach (ToolStripMenuItem menuItem in _menu.Items) + { + if ((DarkDropdownItem)menuItem.Tag == item) + _menu.Items.Remove(menuItem); + } + } + } + + ResizeMenu(); + } + + private void Item_Select(object sender, EventArgs e) + { + var menuItem = sender as ToolStripMenuItem; + if (menuItem == null) + return; + + var dropdownItem = (DarkDropdownItem)menuItem.Tag; + if (_selectedItem != dropdownItem) + SelectedItem = dropdownItem; + } + + private void DarkDropdownList_SelectedItemChanged(object sender, EventArgs e) + { + foreach (ToolStripMenuItem item in _menu.Items) + { + if ((DarkDropdownItem)item.Tag == SelectedItem) + { + item.BackColor = Colors.DarkBlueBackground; + item.Font = new Font(Font, FontStyle.Bold); + } + else + { + item.BackColor = Colors.GreyBackground; + item.Font = new Font(Font, FontStyle.Regular); + } + } + + Invalidate(); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + ResizeMenu(); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (e.Button == MouseButtons.Left) + { + if (ClientRectangle.Contains(e.Location)) + SetControlState(DarkControlState.Pressed); + else + SetControlState(DarkControlState.Hover); + } + else + { + SetControlState(DarkControlState.Hover); + } + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + ShowMenu(); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + SetControlState(DarkControlState.Normal); + } + + protected override void OnMouseLeave(EventArgs e) + { + base.OnMouseLeave(e); + + SetControlState(DarkControlState.Normal); + } + + protected override void OnMouseCaptureChanged(EventArgs e) + { + base.OnMouseCaptureChanged(e); + + var location = Cursor.Position; + + if (!ClientRectangle.Contains(location)) + SetControlState(DarkControlState.Normal); + } + + protected override void OnGotFocus(EventArgs e) + { + base.OnGotFocus(e); + + Invalidate(); + } + + protected override void OnLostFocus(EventArgs e) + { + base.OnLostFocus(e); + + var location = Cursor.Position; + + if (!ClientRectangle.Contains(location)) + SetControlState(DarkControlState.Normal); + else + SetControlState(DarkControlState.Hover); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + if (e.KeyCode == Keys.Space) + ShowMenu(); + } + + private void Menu_Closed(object sender, ToolStripDropDownClosedEventArgs e) + { + _menuOpen = false; + + if (!ClientRectangle.Contains(MousePosition)) + SetControlState(DarkControlState.Normal); + else + SetControlState(DarkControlState.Hover); + } + + #endregion + + #region Render Region + + protected override void OnPaint(PaintEventArgs e) + { + var g = e.Graphics; + + // Draw background + using (var b = new SolidBrush(Colors.MediumBackground)) + { + g.FillRectangle(b, ClientRectangle); + } + + // Draw normal state + if (ControlState == DarkControlState.Normal) + { + if (ShowBorder) + { + using (var p = new Pen(Colors.LightBorder, 1)) + { + var modRect = new Rectangle(ClientRectangle.Left, ClientRectangle.Top, ClientRectangle.Width - 1, ClientRectangle.Height - 1); + g.DrawRectangle(p, modRect); + } + } + } + + // Draw hover state + if (ControlState == DarkControlState.Hover) + { + using (var b = new SolidBrush(Colors.DarkBorder)) + { + g.FillRectangle(b, ClientRectangle); + } + + using (var b = new SolidBrush(Colors.DarkBackground)) + { + var arrowRect = new Rectangle(ClientRectangle.Right - DropdownIcons.small_arrow.Width - 8, ClientRectangle.Top, DropdownIcons.small_arrow.Width + 8, ClientRectangle.Height); + g.FillRectangle(b, arrowRect); + } + + using (var p = new Pen(Colors.BlueSelection, 1)) + { + var modRect = new Rectangle(ClientRectangle.Left, ClientRectangle.Top, ClientRectangle.Width - 1 - DropdownIcons.small_arrow.Width - 8, ClientRectangle.Height - 1); + g.DrawRectangle(p, modRect); + } + } + + // Draw pressed state + if (ControlState == DarkControlState.Pressed) + { + using (var b = new SolidBrush(Colors.DarkBorder)) + { + g.FillRectangle(b, ClientRectangle); + } + + using (var b = new SolidBrush(Colors.BlueSelection)) + { + var arrowRect = new Rectangle(ClientRectangle.Right - DropdownIcons.small_arrow.Width - 8, ClientRectangle.Top, DropdownIcons.small_arrow.Width + 8, ClientRectangle.Height); + g.FillRectangle(b, arrowRect); + } + } + + // Draw dropdown arrow + using (var img = DropdownIcons.small_arrow) + { + g.DrawImageUnscaled(img, ClientRectangle.Right - img.Width - 4, ClientRectangle.Top + (ClientRectangle.Height / 2) - (img.Height / 2)); + } + + // Draw selected item + if (SelectedItem != null) + { + // Draw Icon + var hasIcon = SelectedItem.Icon != null; + + if (hasIcon) + { + g.DrawImageUnscaled(SelectedItem.Icon, new Point(ClientRectangle.Left + 5, ClientRectangle.Top + (ClientRectangle.Height / 2) - (_iconSize / 2))); + } + + // Draw Text + using (var b = new SolidBrush(Colors.LightText)) + { + var stringFormat = new StringFormat + { + Alignment = StringAlignment.Near, + LineAlignment = StringAlignment.Center + }; + + var rect = new Rectangle(ClientRectangle.Left + 2, ClientRectangle.Top, ClientRectangle.Width - 16, ClientRectangle.Height); + + if (hasIcon) + { + rect.X += _iconSize + 7; + rect.Width -= _iconSize + 7; + } + + g.DrawString(SelectedItem.Text, Font, b, rect, stringFormat); + } + } + } + + #endregion + } +} diff --git a/DarkUI/DarkUI.csproj b/DarkUI/DarkUI.csproj index 6f4914b..5d274ca 100644 --- a/DarkUI/DarkUI.csproj +++ b/DarkUI/DarkUI.csproj @@ -50,6 +50,10 @@ Component + + + Component + @@ -161,6 +165,11 @@ True DockIcons.resx + + True + True + DropdownIcons.resx + True True @@ -202,6 +211,11 @@ DockIcons.Designer.cs DarkUI + + ResXFileCodeGenerator + DropdownIcons.Designer.cs + DarkUI + PublicResXFileCodeGenerator MenuIcons.Designer.cs @@ -292,6 +306,9 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\small_arrow.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/DarkUI/Renderers/DarkMenuRenderer.cs b/DarkUI/Renderers/DarkMenuRenderer.cs index 9997e66..ef80e2f 100644 --- a/DarkUI/Renderers/DarkMenuRenderer.cs +++ b/DarkUI/Renderers/DarkMenuRenderer.cs @@ -1,4 +1,5 @@ using DarkUI.Config; +using DarkUI.Controls; using DarkUI.Icons; using System; using System.Drawing; @@ -22,6 +23,7 @@ namespace DarkUI.Renderers { base.InitializeItem(item); + item.BackColor = Colors.GreyBackground; item.ForeColor = Colors.LightText; if (item.GetType() == typeof(ToolStripSeparator)) @@ -107,15 +109,15 @@ namespace DarkUI.Renderers if (e.Item.Enabled) { - // Normal item - if (e.Item.Selected) - { - var rect = new Rectangle(2, 0, e.Item.Width - 3, e.Item.Height); + + var bgColor = e.Item.Selected ? Colors.GreyHighlight : e.Item.BackColor; - using (var b = new SolidBrush(Colors.GreySelection)) - { - g.FillRectangle(b, rect); - } + // Normal item + var rect = new Rectangle(2, 0, e.Item.Width - 3, e.Item.Height); + + using (var b = new SolidBrush(bgColor)) + { + g.FillRectangle(b, rect); } // Header item on open menu @@ -123,8 +125,6 @@ namespace DarkUI.Renderers { if (((ToolStripMenuItem)e.Item).DropDown.Visible && e.Item.IsOnDropDown == false) { - var rect = new Rectangle(2, 0, e.Item.Width - 3, e.Item.Height); - using (var b = new SolidBrush(Colors.GreySelection)) { g.FillRectangle(b, rect); diff --git a/DarkUI/Resources/small_arrow.png b/DarkUI/Resources/small_arrow.png new file mode 100644 index 0000000..4824816 Binary files /dev/null and b/DarkUI/Resources/small_arrow.png differ diff --git a/Example/Forms/Docking/DockDocument.Designer.cs b/Example/Forms/Docking/DockDocument.Designer.cs index 8f6a77d..d5d706d 100644 --- a/Example/Forms/Docking/DockDocument.Designer.cs +++ b/Example/Forms/Docking/DockDocument.Designer.cs @@ -29,6 +29,7 @@ private void InitializeComponent() { this.txtDocument = new System.Windows.Forms.TextBox(); + this.cmbOptions = new DarkUI.Controls.DarkDropdownList(); this.SuspendLayout(); // // txtDocument @@ -44,10 +45,24 @@ this.txtDocument.TabIndex = 1; this.txtDocument.Text = "This is some example text"; // + // cmbOptions + // + this.cmbOptions.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.cmbOptions.DropdownDirection = System.Windows.Forms.ToolStripDropDownDirection.AboveRight; + this.cmbOptions.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmbOptions.Location = new System.Drawing.Point(0, 158); + this.cmbOptions.MaxHeight = 300; + this.cmbOptions.Name = "cmbOptions"; + this.cmbOptions.ShowBorder = false; + this.cmbOptions.Size = new System.Drawing.Size(65, 15); + this.cmbOptions.TabIndex = 2; + this.cmbOptions.Text = "darkComboBox1"; + // // DockDocument // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.cmbOptions); this.Controls.Add(this.txtDocument); this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.Name = "DockDocument"; @@ -60,5 +75,6 @@ #endregion private System.Windows.Forms.TextBox txtDocument; + private DarkUI.Controls.DarkDropdownList cmbOptions; } } diff --git a/Example/Forms/Docking/DockDocument.cs b/Example/Forms/Docking/DockDocument.cs index 1b737ca..fe8fa72 100644 --- a/Example/Forms/Docking/DockDocument.cs +++ b/Example/Forms/Docking/DockDocument.cs @@ -1,4 +1,5 @@ using DarkUI.Config; +using DarkUI.Controls; using DarkUI.Docking; using DarkUI.Forms; using System.Drawing; @@ -16,6 +17,14 @@ namespace Example // Workaround to stop the textbox from highlight all text. txtDocument.SelectionStart = txtDocument.Text.Length; + + // Build dummy dropdown data + cmbOptions.Items.Add(new DarkDropdownItem("25%")); + cmbOptions.Items.Add(new DarkDropdownItem("50%")); + cmbOptions.Items.Add(new DarkDropdownItem("100%")); + cmbOptions.Items.Add(new DarkDropdownItem("200%")); + cmbOptions.Items.Add(new DarkDropdownItem("300%")); + cmbOptions.Items.Add(new DarkDropdownItem("400%")); } public DockDocument(string text, Image icon) diff --git a/Example/Forms/Docking/DockLayers.Designer.cs b/Example/Forms/Docking/DockLayers.Designer.cs index 1e43698..169d9cc 100644 --- a/Example/Forms/Docking/DockLayers.Designer.cs +++ b/Example/Forms/Docking/DockLayers.Designer.cs @@ -32,23 +32,35 @@ namespace Example private void InitializeComponent() { this.lstLayers = new DarkUI.Controls.DarkListView(); + this.cmbList = new DarkUI.Controls.DarkDropdownList(); this.SuspendLayout(); // // lstLayers // this.lstLayers.Dock = System.Windows.Forms.DockStyle.Fill; - this.lstLayers.Location = new System.Drawing.Point(0, 25); + this.lstLayers.Location = new System.Drawing.Point(0, 51); this.lstLayers.Name = "lstLayers"; this.lstLayers.ShowIcons = true; - this.lstLayers.Size = new System.Drawing.Size(280, 425); + this.lstLayers.Size = new System.Drawing.Size(280, 399); this.lstLayers.TabIndex = 0; this.lstLayers.Text = "darkListView1"; // + // cmbList + // + this.cmbList.Dock = System.Windows.Forms.DockStyle.Top; + this.cmbList.Location = new System.Drawing.Point(0, 25); + this.cmbList.Name = "cmbList"; + this.cmbList.ShowBorder = false; + this.cmbList.Size = new System.Drawing.Size(280, 26); + this.cmbList.TabIndex = 1; + this.cmbList.Text = "darkDropdownList1"; + // // DockLayers // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Controls.Add(this.lstLayers); + this.Controls.Add(this.cmbList); this.DefaultDockArea = DarkUI.Docking.DarkDockArea.Right; this.DockText = "Layers"; this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); @@ -63,5 +75,6 @@ namespace Example #endregion private DarkListView lstLayers; + private DarkDropdownList cmbList; } } diff --git a/Example/Forms/Docking/DockLayers.cs b/Example/Forms/Docking/DockLayers.cs index 2d650f7..ebe0440 100644 --- a/Example/Forms/Docking/DockLayers.cs +++ b/Example/Forms/Docking/DockLayers.cs @@ -1,4 +1,5 @@ -using DarkUI.Controls; +using System; +using DarkUI.Controls; using DarkUI.Docking; namespace Example @@ -18,6 +19,12 @@ namespace Example item.Icon = Icons.application_16x; lstLayers.Items.Add(item); } + + // Build dropdown list data + for (var i = 0; i < 5; i++) + { + cmbList.Items.Add(new DarkDropdownItem($"Dropdown item #{i}")); + } } #endregion