diff --git a/DarkUI/Controls/Classes/DarkListItem.cs b/DarkUI/Controls/Classes/DarkListItem.cs new file mode 100644 index 0000000..6b320c6 --- /dev/null +++ b/DarkUI/Controls/Classes/DarkListItem.cs @@ -0,0 +1,32 @@ +using System.Drawing; + +namespace DarkUI +{ + public class DarkListItem + { + #region Property Region + + public string Text { get; set; } + public Rectangle Area { get; set; } + public Color TextColor { get; set; } + public FontStyle FontStyle { get; set; } + + #endregion + + #region Constructor Region + + public DarkListItem() + { + TextColor = Colors.LightText; + FontStyle = FontStyle.Regular; + } + + public DarkListItem(string text) + : this() + { + Text = text; + } + + #endregion + } +} diff --git a/DarkUI/Controls/DarkListView.cs b/DarkUI/Controls/DarkListView.cs new file mode 100644 index 0000000..326149f --- /dev/null +++ b/DarkUI/Controls/DarkListView.cs @@ -0,0 +1,474 @@ +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 +{ + public class DarkListView : DarkScrollView + { + #region Field Region + + private int _itemHeight = 20; + private bool _multiSelect; + + private ObservableCollection _items; + private List _selectedIndices; + private int _anchoredItemStart = -1; + private int _anchoredItemEnd = -1; + + #endregion + + #region Property Region + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ObservableCollection Items + { + get { return _items; } + set + { + if (_items != null) + _items.CollectionChanged -= Items_CollectionChanged; + + _items = value; + + _items.CollectionChanged += Items_CollectionChanged; + + UpdateListBox(); + } + } + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public List SelectedIndices + { + get { return _selectedIndices; } + } + + [Category("Appearance")] + [Description("Determines the height of the individual list view items.")] + [DefaultValue(20)] + public int ItemHeight + { + get { return _itemHeight; } + set + { + _itemHeight = value; + UpdateListBox(); + } + } + + [Category("Behaviour")] + [Description("Determines whether multiple list view items can be selected at once.")] + [DefaultValue(false)] + public bool MultiSelect + { + get { return _multiSelect; } + set { _multiSelect = value; } + } + + #endregion + + #region Constructor Region + + public DarkListView() + { + Items = new ObservableCollection(); + _selectedIndices = new List(); + } + + #endregion + + #region Event Handler Region + + private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + using (var g = CreateGraphics()) + { + // Set the area size of all new items + foreach (DarkListItem item in e.NewItems) + UpdateItemSize(item, g); + } + + // Find the starting index of the new item list and update anything past that + if (e.NewStartingIndex < (Items.Count - 1)) + { + for (var i = e.NewStartingIndex; i <= Items.Count - 1; i++) + { + UpdateItemPosition(Items[i], i); + } + } + } + + if (e.OldItems != null) + { + // Find the starting index of the old item list and update anything past that + if (e.OldStartingIndex < (Items.Count - 1)) + { + for (var i = e.NewStartingIndex; i <= Items.Count - 1; i++) + { + UpdateItemPosition(Items[i], i); + } + } + } + + UpdateContentSize(); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (Items.Count == 0) + return; + + if (e.Button != MouseButtons.Left && e.Button != MouseButtons.Right) + return; + + var pos = OffsetMousePosition; + + var range = ItemIndexesInView().ToList(); + + var top = range.Min(); + var bottom = range.Max(); + var width = Math.Max(ContentSize.Width, Viewport.Width); + + for (var i = top; i <= bottom; i++) + { + var rect = new Rectangle(0, i * ItemHeight, width, ItemHeight); + + if (rect.Contains(pos)) + { + if (MultiSelect && ModifierKeys == Keys.Shift) + SelectAnchoredRange(i); + else if (MultiSelect && ModifierKeys == Keys.Control) + ToggleItem(i); + else + SelectItem(i); + } + } + } + + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + if (Items.Count == 0) + return; + + if (e.KeyCode != Keys.Down && e.KeyCode != Keys.Up) + return; + + if (MultiSelect && ModifierKeys == Keys.Shift) + { + if (e.KeyCode == Keys.Up) + { + if (_anchoredItemEnd - 1 >= 0) + { + SelectAnchoredRange(_anchoredItemEnd - 1); + EnsureVisible(); + } + } + else if (e.KeyCode == Keys.Down) + { + if (_anchoredItemEnd + 1 <= Items.Count - 1) + { + SelectAnchoredRange(_anchoredItemEnd + 1); + } + } + } + else + { + if (e.KeyCode == Keys.Up) + { + if (_anchoredItemEnd - 1 >= 0) + SelectItem(_anchoredItemEnd - 1); + } + else if (e.KeyCode == Keys.Down) + { + if (_anchoredItemEnd + 1 <= Items.Count - 1) + SelectItem(_anchoredItemEnd + 1); + } + } + + EnsureVisible(); + } + + #endregion + + #region Method Region + + public void SelectItem(int index) + { + if (index < 0 || index > Items.Count - 1) + throw new IndexOutOfRangeException(string.Format("Value '{0}' is outside of valid range.", index)); + + _selectedIndices.Clear(); + _selectedIndices.Add(index); + + _anchoredItemStart = index; + _anchoredItemEnd = index; + + Invalidate(); + } + + public void SelectItems(IEnumerable indexes) + { + _selectedIndices.Clear(); + + var list = indexes.ToList(); + + foreach (var index in list) + { + if (index < 0 || index > Items.Count - 1) + throw new IndexOutOfRangeException(string.Format("Value '{0}' is outside of valid range.", index)); + + _selectedIndices.Add(index); + } + + _anchoredItemStart = list[list.Count - 1]; + _anchoredItemEnd = list[list.Count - 1]; + + Invalidate(); + } + + public void ToggleItem(int index) + { + if (_selectedIndices.Contains(index)) + { + _selectedIndices.Remove(index); + + // If we just removed both the anchor start AND end then reset them + if (_anchoredItemStart == index && _anchoredItemEnd == index) + { + if (_selectedIndices.Count > 0) + { + _anchoredItemStart = _selectedIndices[0]; + _anchoredItemEnd = _selectedIndices[0]; + } + else + { + _anchoredItemStart = -1; + _anchoredItemEnd = -1; + } + } + + // If we just removed the anchor start then update it accordingly + if (_anchoredItemStart == index) + { + if (_anchoredItemEnd < index) + _anchoredItemStart = index - 1; + else if (_anchoredItemEnd > index) + _anchoredItemStart = index + 1; + else + _anchoredItemStart = _anchoredItemEnd; + } + + // If we just removed the anchor end then update it accordingly + if (_anchoredItemEnd == index) + { + if (_anchoredItemStart < index) + _anchoredItemEnd = index - 1; + else if (_anchoredItemStart > index) + _anchoredItemEnd = index + 1; + else + _anchoredItemEnd = _anchoredItemStart; + } + } + else + { + _selectedIndices.Add(index); + _anchoredItemStart = index; + _anchoredItemEnd = index; + } + + Invalidate(); + } + + public void SelectItems(int startRange, int endRange) + { + _selectedIndices.Clear(); + + if (startRange == endRange) + _selectedIndices.Add(startRange); + + if (startRange < endRange) + { + for (var i = startRange; i <= endRange; i++) + _selectedIndices.Add(i); + } + else if (startRange > endRange) + { + for (var i = startRange; i >= endRange; i--) + _selectedIndices.Add(i); + } + + Invalidate(); + } + + private void SelectAnchoredRange(int index) + { + _anchoredItemEnd = index; + SelectItems(_anchoredItemStart, index); + } + + private void UpdateListBox() + { + using (var g = CreateGraphics()) + { + for (var i = 0; i <= Items.Count - 1; i++) + { + var item = Items[i]; + UpdateItemSize(item, g); + UpdateItemPosition(item, i); + } + } + + UpdateContentSize(); + } + + private void UpdateItemSize(DarkListItem item) + { + using (var g = CreateGraphics()) + { + UpdateItemSize(item, g); + } + } + + private void UpdateItemSize(DarkListItem item, Graphics g) + { + var size = g.MeasureString(item.Text, Font); + size.Width++; + + item.Area = new Rectangle(item.Area.Left, item.Area.Top, (int)size.Width, item.Area.Height); + } + + private void UpdateItemPosition(DarkListItem item, int index) + { + item.Area = new Rectangle(2, (index * ItemHeight), item.Area.Width, ItemHeight); + } + + private void UpdateContentSize() + { + var highestWidth = 0; + + foreach (var item in Items) + { + if (item.Area.Right + 1 > highestWidth) + highestWidth = item.Area.Right + 1; + } + + ContentSize = new Size(highestWidth, Items.Count * ItemHeight); + + Invalidate(); + } + + public void EnsureVisible() + { + if (SelectedIndices.Count == 0) + return; + + var itemTop = -1; + + if (!MultiSelect) + itemTop = SelectedIndices[0] * ItemHeight; + else + itemTop = _anchoredItemEnd * ItemHeight; + + var itemBottom = itemTop + ItemHeight; + + if (itemTop < Viewport.Top) + VScrollTo(itemTop); + + if (itemBottom > Viewport.Bottom) + VScrollTo((itemBottom - Viewport.Height)); + } + + private IEnumerable ItemIndexesInView() + { + var top = (Viewport.Top / ItemHeight) - 1; + + if (top < 0) + top = 0; + + var bottom = ((Viewport.Top + Viewport.Height) / ItemHeight) + 1; + + if (bottom > Items.Count) + bottom = Items.Count; + + var result = Enumerable.Range(top, bottom - top); + return result; + } + + private IEnumerable ItemsInView() + { + var indexes = ItemIndexesInView(); + var result = indexes.Select(index => Items[index]).ToList(); + return result; + } + + #endregion + + #region Paint Region + + protected override void PaintContent(Graphics g) + { + var range = ItemIndexesInView().ToList(); + + if (range.Count == 0) + return; + + var top = range.Min(); + var bottom = range.Max(); + + // Draw items + for (var i = top; i <= bottom; i++) + { + var width = Math.Max(ContentSize.Width, Viewport.Width); + var rect = new Rectangle(0, i * ItemHeight, width, ItemHeight); + + // Background + var odd = i % 2 != 0; + var bgColor = !odd ? Colors.HeaderBackground : Colors.GreyBackground; + + if (SelectedIndices.Count > 0 && SelectedIndices.Contains(i)) + bgColor = Focused ? Colors.BlueSelection : Colors.GreySelection; + + using (var b = new SolidBrush(bgColor)) + { + g.FillRectangle(b, rect); + } + + // Border + /*using (var p = new Pen(Colors.DarkBorder)) + { + g.DrawLine(p, new Point(rect.Left, rect.Bottom - 1), new Point(rect.Right, rect.Bottom - 1)); + }*/ + + // Text + using (var b = new SolidBrush(Items[i].TextColor)) + { + var stringFormat = new StringFormat + { + Alignment = StringAlignment.Near, + LineAlignment = StringAlignment.Center + }; + + var modFont = new Font(Font, Items[i].FontStyle); + + var modRect = new Rectangle(rect.Left + 2, rect.Top, rect.Width, rect.Height); + g.DrawString(Items[i].Text, modFont, b, modRect, stringFormat); + } + } + } + + #endregion + } +} diff --git a/DarkUI/Controls/DarkScrollBase.cs b/DarkUI/Controls/DarkScrollBase.cs index 9456b20..df0d409 100644 --- a/DarkUI/Controls/DarkScrollBase.cs +++ b/DarkUI/Controls/DarkScrollBase.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Drawing; using System.Windows.Forms; @@ -29,6 +30,8 @@ namespace DarkUI #region Property Region + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Rectangle Viewport { get { return _viewport; } @@ -41,6 +44,8 @@ namespace DarkUI } } + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Size ContentSize { get { return _contentSize; } @@ -54,6 +59,8 @@ namespace DarkUI } } + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Point OffsetMousePosition { get { return _offsetMousePosition; } diff --git a/DarkUI/DarkUI.csproj b/DarkUI/DarkUI.csproj index 4b113af..246636e 100644 --- a/DarkUI/DarkUI.csproj +++ b/DarkUI/DarkUI.csproj @@ -41,6 +41,10 @@ + + + Component + Component @@ -59,6 +63,9 @@ Component + + Component + Component diff --git a/Example/Forms/MainForm.Designer.cs b/Example/Forms/MainForm.Designer.cs index f19fd8c..91bddd9 100644 --- a/Example/Forms/MainForm.Designer.cs +++ b/Example/Forms/MainForm.Designer.cs @@ -84,11 +84,14 @@ this.testToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.moreTestToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.test3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.darkSectionPanel2 = new DarkUI.DarkSectionPanel(); + this.darkListView1 = new DarkUI.DarkListView(); this.mnuMain.SuspendLayout(); this.toolMain.SuspendLayout(); this.darkStatusStrip1.SuspendLayout(); this.darkSectionPanel1.SuspendLayout(); this.darkContextMenu1.SuspendLayout(); + this.darkSectionPanel2.SuspendLayout(); this.SuspendLayout(); // // mnuMain @@ -590,7 +593,7 @@ // this.testToolStripMenuItem.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(220)))), ((int)(((byte)(220))))); this.testToolStripMenuItem.Name = "testToolStripMenuItem"; - this.testToolStripMenuItem.Size = new System.Drawing.Size(152, 22); + this.testToolStripMenuItem.Size = new System.Drawing.Size(124, 22); this.testToolStripMenuItem.Text = "Test"; // // moreTestToolStripMenuItem @@ -607,11 +610,31 @@ this.test3ToolStripMenuItem.Size = new System.Drawing.Size(124, 22); this.test3ToolStripMenuItem.Text = "Test 3"; // + // darkSectionPanel2 + // + this.darkSectionPanel2.Controls.Add(this.darkListView1); + this.darkSectionPanel2.Location = new System.Drawing.Point(204, 157); + this.darkSectionPanel2.Name = "darkSectionPanel2"; + this.darkSectionPanel2.SectionHeader = "List view test"; + this.darkSectionPanel2.Size = new System.Drawing.Size(221, 224); + this.darkSectionPanel2.TabIndex = 7; + // + // darkListView1 + // + this.darkListView1.Dock = System.Windows.Forms.DockStyle.Fill; + this.darkListView1.Location = new System.Drawing.Point(1, 25); + this.darkListView1.MultiSelect = true; + this.darkListView1.Name = "darkListView1"; + this.darkListView1.Size = new System.Drawing.Size(219, 198); + this.darkListView1.TabIndex = 7; + this.darkListView1.Text = "darkListView1"; + // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(784, 562); + this.Controls.Add(this.darkSectionPanel2); this.Controls.Add(this.darkSectionPanel1); this.Controls.Add(this.btnDialog); this.Controls.Add(this.darkStatusStrip1); @@ -631,6 +654,7 @@ this.darkStatusStrip1.PerformLayout(); this.darkSectionPanel1.ResumeLayout(false); this.darkContextMenu1.ResumeLayout(false); + this.darkSectionPanel2.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); @@ -693,6 +717,8 @@ private System.Windows.Forms.ToolStripMenuItem testToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem moreTestToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem test3ToolStripMenuItem; + private DarkUI.DarkSectionPanel darkSectionPanel2; + private DarkUI.DarkListView darkListView1; } } diff --git a/Example/Forms/MainForm.cs b/Example/Forms/MainForm.cs index 99360f1..9cb6a70 100644 --- a/Example/Forms/MainForm.cs +++ b/Example/Forms/MainForm.cs @@ -8,6 +8,12 @@ namespace Example { InitializeComponent(); + for (var i = 0; i < 100; i++) + { + var item = new DarkListItem(string.Format("List item {0}", i)); + darkListView1.Items.Add(item); + } + btnDialog.Click += delegate { DarkMessageBox.ShowError("This is an error", "Dark UI - Example");