winform下重画ListBox
Windows Forms是由Win32 API封装的开发组件,最初是为了替代mfc,但却没有体现与Model View Controller架构对应的特色,进而在.net framework 3.0中推出了wpf,富控件数据显示方面,利用模板功能轻松实现。
在winform下要想自定义一些用户控件,就需要运用的2D绘画类。下图我们为ListBox重新排列了数据显示方式,并为每一个item加入了删除按钮。
首先我们设计一个承载数据的类ListBoxItem。
1 public class ListBoxItem : IDisposable 2 { 3 public Guid Id { get; set; } 4 5 public string Name { get; set; } 6 7 public string IP { get; set; } 8 9 public string Mac { get; set; } 10 11 [System.ComponentModel.DefaultValue(typeof(System.Drawing.Image), "null")] 12 public System.Drawing.Image Image { get; set; } 13 14 public bool IsFocus { get; set; } 15 16 public ListBoxItem() { } 17 18 public ListBoxItem(Guid id, string name, string ip, string mac, System.Drawing.Image image) 19 { 20 this.Id = id; 21 this.Name = name; 22 this.IP = ip; 23 this.Mac = mac; 24 this.Image = image; 25 this.IsFocus = false; 26 } 27 28 public void Dispose() 29 { 30 this.Image = null; 31 } 32 }
然后我们再为ListBox写一个用于展现数据的数据源ListBoxItemCollection,这里实现了迭代和集合操作接口,可以根据需要扩展数据操作方法。
[System.ComponentModel.ListBindable(false)] public class ListBoxItemCollection : IList, ICollection, IEnumerable { private UserListBox m_owner; public ListBoxItemCollection(UserListBox owner) { this.m_owner = owner; } internal UserListBox Owner { get { return this.m_owner; } } #region override public ListBoxItem this[int index] { get { return Owner.OldItemSource[index] as ListBoxItem; } set { Owner.OldItemSource[index] = value; } } public int Count { get { return Owner.OldItemSource.Count; } } public bool IsReadOnly { get { return Owner.OldItemSource.IsReadOnly; } } public int Add(ListBoxItem item) { if (item == null) { throw new ArgumentException("item is null"); } return Owner.OldItemSource.Add(item); } public void AddRange(ListBoxItem[] items) { Owner.OldItemSource.AddRange(items); } public void Clear() { if (Owner.OldItemSource.Count > 0) { Owner.OldItemSource.Clear(); } } public bool Contains(ListBoxItem item) { bool rst = false; foreach (ListBoxItem oldItem in Owner.OldItemSource) { if (oldItem.Id == item.Id) { rst = true; break; } } return rst; } public void CopyTo(ListBoxItem[] destination, int arrayIndex) { Owner.OldItemSource.CopyTo(destination, arrayIndex); } public int IndexOf(ListBoxItem item) { return Owner.OldItemSource.IndexOf(item); } public void Insert(int index, ListBoxItem item) { if (item == null) { throw new ArgumentException("item is null"); } Owner.OldItemSource.Insert(index, item); } public void Remove(ListBoxItem item) { Owner.OldItemSource.Remove(item); } public void RemoveAt(int index) { Owner.OldItemSource.RemoveAt(index); } public IEnumerator GetEnumerator() { return Owner.OldItemSource.GetEnumerator(); } int IList.Add(object value) { if (!(value is ListBoxItem)) { throw new ArgumentException(); } return Add(value as ListBoxItem); } void IList.Clear() { Clear(); } bool IList.Contains(object value) { return Contains(value as ListBoxItem); } int IList.IndexOf(object value) { return IndexOf(value as ListBoxItem); } void IList.Insert(int index, object value) { if (!(value is ListBoxItem)) { throw new ArgumentException(); } Insert(index, value as ListBoxItem); } bool IList.IsFixedSize { get { return false; } } bool IList.IsReadOnly { get { return IsReadOnly; } } void IList.Remove(object value) { Remove(value as ListBoxItem); } void IList.RemoveAt(int index) { RemoveAt(index); } object IList.this[int index] { get { return this[index]; } set { if (!(value is ListBoxItem)) { throw new ArgumentException(); } this[index] = value as ListBoxItem; } } void ICollection.CopyTo(Array array, int index) { CopyTo((ListBoxItem[])array, index); } int ICollection.Count { get { return Count; } } bool ICollection.IsSynchronized { get { return false; } } object ICollection.SyncRoot { get { return false; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region extention public ListBoxItem FindByMac(string mac) { foreach (ListBoxItem item in Owner.OldItemSource) { if (item.Mac == mac) { return item; } } return null; } #endregion }
下面可以为工程new一个新项——自定义控件,命名为UserListBox。
这里有几个地方要说明一下,首先在默认构造函数里面的参数:
DrawMode.OwnerDrawVariable启用控件重绘功能。
DoubleBuffer开启后避免复杂绘画造成窗体闪烁,这个缓冲的原理是将绘画操作放在内存里操作,完成后才会复制到图形界面上,进而避免的闪烁。
OnPaint进行了重写,这个方法是根据pc屏幕分辨率刷新频率来执行的,会不断的重复执行,进而持久化图形界面。
Invalidate方法,会立即刷新UI。
Item上的按钮事件,是通过ListBox的click事件,取到鼠标的在界面上的定位,调用相对应的方法。
1 public partial class UserListBox : ListBox 2 { 3 public ListBoxItem mouseItem; 4 private ListBoxItemCollection m_Items; 5 6 public UserListBox() : base() 7 { 8 InitializeComponent(); 9 10 m_Items = new ListBoxItemCollection(this); 11 12 base.DrawMode = DrawMode.OwnerDrawVariable; 13 this.SetStyle(ControlStyles.UserPaint, true); 14 this.SetStyle(ControlStyles.DoubleBuffer, true); // 双缓冲 15 this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true); // 双缓冲 16 this.SetStyle(ControlStyles.ResizeRedraw, true); // 调整大小时重绘 17 this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 禁止擦除背景. 18 this.SetStyle(ControlStyles.SupportsTransparentBackColor, true); // 开启控件透明 19 } 20 21 public new ListBoxItemCollection Items 22 { 23 get { return m_Items; } 24 } 25 26 internal ListBox.ObjectCollection OldItemSource 27 { 28 get { return base.Items; } 29 } 30 31 protected override void OnPaint(PaintEventArgs e) 32 { 33 Graphics g = e.Graphics; 34 35 // you can set SeletedItem background 36 if (this.Focused && this.SelectedItem != null) 37 { 38 } 39 40 for (int i = 0; i < Items.Count; i++) 41 { 42 Rectangle bounds = this.GetItemRectangle(i); 43 44 if (mouseItem == Items[i]) 45 { 46 Color leftColor = Color.FromArgb(200, 192, 224, 248); 47 using (SolidBrush brush = new SolidBrush(leftColor)) 48 { 49 g.FillRectangle(brush, new Rectangle(bounds.X, bounds.Y, bounds.Width, bounds.Height)); 50 } 51 52 Color rightColor = Color.FromArgb(252, 233, 161); 53 using (SolidBrush brush = new SolidBrush(rightColor)) 54 { 55 g.FillRectangle(brush, new Rectangle(bounds.Width - 40, bounds.Y, 40, bounds.Height)); 56 } 57 } 58 59 int fontLeft = bounds.Left + 40 + 15; 60 System.Drawing.Font font = new System.Drawing.Font("微软雅黑", 9); 61 g.DrawString(Items[i].Name, font, new SolidBrush(this.ForeColor), fontLeft, bounds.Top + 5); 62 g.DrawString(Items[i].IP, font, new SolidBrush(Color.FromArgb(128, 128, 128)), fontLeft, bounds.Top + 20); 63 g.DrawString(Items[i].Mac, font, new SolidBrush(Color.FromArgb(128, 128, 128)), fontLeft, bounds.Top + 35); 64 65 if (Items[i].Image != null) 66 { 67 g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear; 68 g.DrawImage(Items[i].Image, new Rectangle(bounds.X + 5, (bounds.Height - 40) / 2 + bounds.Top, 40, 40)); 69 } 70 g.DrawImage(Properties.Resources.error, new Rectangle(bounds.Width - 28, (bounds.Height - 16) / 2 + bounds.Top, 16, 16)); 71 } 72 base.OnPaint(e); 73 } 74 75 protected override void OnMeasureItem(MeasureItemEventArgs e) 76 { 77 base.OnMeasureItem(e); 78 if (Items.Count > 0) 79 { 80 ListBoxItem item = Items[e.Index]; 81 e.ItemHeight = 54; 82 } 83 84 } 85 86 protected override void OnSelectedIndexChanged(EventArgs e) 87 { 88 base.OnSelectedIndexChanged(e); 89 } 90 91 protected override void OnMouseMove(MouseEventArgs e) 92 { 93 base.OnMouseMove(e); 94 for (int i = 0; i < Items.Count; i++) 95 { 96 Rectangle bounds = this.GetItemRectangle(i); 97 Rectangle deleteBounds = new Rectangle(bounds.Width - 28, (bounds.Height - 16) / 2 + bounds.Top, 16, 16); 98 99 if (bounds.Contains(e.X, e.Y)) 100 { 101 if (Items[i] != mouseItem) 102 { 103 mouseItem = Items[i]; 104 } 105 106 if (deleteBounds.Contains(e.X, e.Y)) 107 { 108 mouseItem.IsFocus = true; 109 this.Cursor = Cursors.Hand; 110 } 111 else 112 { 113 mouseItem.IsFocus = false; 114 this.Cursor = Cursors.Arrow; 115 } 116 117 this.Invalidate(); 118 break; 119 } 120 } 121 } 122 123 protected override void OnMouseClick(MouseEventArgs e) 124 { 125 base.OnMouseClick(e); 126 if (mouseItem.IsFocus) 127 { 128 ListBoxItem deleteItem = mouseItem; 129 if(MessageBox.Show("confirm to delete", "", MessageBoxButtons.OKCancel) == DialogResult.OK) 130 { 131 this.Items.Remove(deleteItem); 132 } 133 } 134 } 135 136 protected override void OnMouseLeave(EventArgs e) 137 { 138 base.OnMouseLeave(e); 139 this.mouseItem = null; 140 this.Invalidate(); 141 } 142 }