DarkListView

This commit is contained in:
Robin 2015-09-18 12:32:23 +01:00
parent 431cea92c8
commit 8408c47a3f
6 changed files with 553 additions and 1 deletions

View File

@ -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
}
}

View File

@ -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<DarkListItem> _items;
private List<int> _selectedIndices;
private int _anchoredItemStart = -1;
private int _anchoredItemEnd = -1;
#endregion
#region Property Region
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ObservableCollection<DarkListItem> 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<int> 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<DarkListItem>();
_selectedIndices = new List<int>();
}
#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<int> 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<int> 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<DarkListItem> 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
}
}

View File

@ -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; }

View File

@ -41,6 +41,10 @@
<Compile Include="Config\Colors.cs" />
<Compile Include="Config\Consts.cs" />
<Compile Include="Config\Enums.cs" />
<Compile Include="Controls\Classes\DarkListItem.cs" />
<Compile Include="Controls\DarkListView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\DarkScrollBase.cs">
<SubType>Component</SubType>
</Compile>
@ -59,6 +63,9 @@
<Compile Include="Controls\DarkScrollBar.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\DarkScrollView.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\DarkSectionPanel.cs">
<SubType>Component</SubType>
</Compile>

View File

@ -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;
}
}

View File

@ -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");