之前在寫播放器的時候,遇到了一個問題,現在播放器無論是千千,KuGoo還是比較原始的MediaPlayer,它們的播放表都是可以拖拽的,直接把文件拖到播放表實現歌曲的添加那個先暫且不說,光是播放表里面的歌曲次序也可以通過拖拽來調整。但是VS提供的ListBox沒能直接通過設定某個屬性實現這個拖拽排序,於是俺就開始了實現這功能的探索,無意中還找到了ListBox與ListBox之間元素的拖拽,於是一並實現了,遂述此文以記之。
其實無論是ListBox里的拖拽排序,還是ListBox間的拖動,都是通過三個事件來實現的:DragDrop,DragOver和MouseDown,對於整個拖拽的過程來說,三個事件的觸發順序是MouseDown->DragOver->DragDrop。對於拖拽排序和控件間的拖動,代碼會有所差異。下面則一分為二的說說各自的處理,由於我這里是擴展控件的,直接重寫ListBox類的Onxxx方法。如果直接使用ListBox控件的話,就要在這三個事件綁定的方法里面寫了。
拖拽排序
protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDoubleClick(e); if (this.Items.Count == 0 || e.Button != MouseButtons.Left || this.SelectedIndex == -1 || e.Clicks == 2) return; int index = this.SelectedIndex; object item = this.Items[index]; DragDropEffects dde = DoDragDrop(item, DragDropEffects.All); }
首先要判斷一下當前的ListBox有沒有元素,還有鼠標有沒有點中元素。接着到拖拽的過程
protected override void OnDragOver(DragEventArgs drgevent) { base.OnDragOver(drgevent); drgevent.Effect = DragDropEffects.Move; }
最后到拖放結束,鼠標按鍵松開的時候觸發
protected override void OnDragDrop(DragEventArgs drgevent) { base.OnDragDrop(drgevent); object item = dragSource.SelectedItem; int index = this.IndexFromPoint(this.PointToClient(new Point(drgevent.X, drgevent.Y))); this.Items.Remove(item); if (index < 0) this.Items.Add(item); else this.Items.Insert(index, item); }
主要是獲取那個被選中(后來就被拖動)的那個元素,然后把它從ListBox先移除,從鼠標當前的坐標來獲取到所在元素的索引值,那個位置,那個索引就是被拖拽的元素的新位置,用Insert把它插進去。
ListBox間的拖動
這個就要先知道各個方法究竟是那個控件的事件觸發時調用的。其余大體上跟上面的是一致的。
舉個例子,現在有兩個ListBox lst1,lst2。我把lst1的一個元素拖到lst2中去,事件觸發的隊列是這樣的
Lst1.MouseDown=>lst1.DragOver=>lst1.DragOver=>….lst1.DragOver=>lst2.DragOver=>lst2.DragOver=>…..=>lst2.DragOver=>lst2.DragDrop
由於整個流程涉及到兩個控件,最后元素的添加與元素的刪除分別是兩個控件的事,於是我這里就另外聲明多一個靜態字段來存放那個lst1。記錄來源的ListBox只能在MouseDown記錄了。
public static ListBox dragSource; protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDoubleClick(e); if (this.Items.Count == 0 || e.Button != MouseButtons.Left || this.SelectedIndex == -1 || e.Clicks == 2) return; dragSource = this; int index = this.SelectedIndex; object item = this.Items[index]; DragDropEffects dde = DoDragDrop(item, DragDropEffects.All); }
除了dragSource = this;外,其余都與拖拽排序的一樣。
protected override void OnDragOver(DragEventArgs drgevent) { base.OnDragOver(drgevent); drgevent.Effect = DragDropEffects.Move; }
這個可以說跟拖拽排序的一模一樣了。
protected override void OnDragDrop(DragEventArgs drgevent) { base.OnDragDrop(drgevent); object item = dragSource.SelectedItem; if ( dragSource != this) { dragSource.Items.Remove(item); this.Items.Add(item); } }
因為是ListBox間的拖動,所以源ListBox和目標ListBox不能一樣,處理還更簡單,從源ListBox把元素刪掉,然后增加到當前的ListBox中來。
如果想要既要控件間的拖動,拖動后又要按位置插入,那就把兩個處理融合一下咯,本文末尾有我拓展控件的整份源碼。需要的園友可以展開來看一下。
交替顏色
下面還介紹我另外一個拓展,就是單雙行的交替顏色。記得以前的千千的播放表是有交替顏色的,現在的不知道,太久沒用了,KuGoo現在的沒有了,MediaPlayer12的也沒有。
我這里是重寫OnDrawItem,直接拖控件的可以用DrawItem時間,接下來就是GDI+的內容了。
protected override void OnDrawItem(DrawItemEventArgs e) { if (this.Items.Count < 0) return; if (e.Index < 0) return; bool selected = (e.State & DrawItemState.Selected) == DrawItemState.Selected; if(selected) e.Graphics.FillRectangle(selectRowBursh, e.Bounds); else if (e.Index % 2 != 0) e.Graphics.FillRectangle(OddRowBursh, e.Bounds); else e.Graphics.FillRectangle(EvenRowBursh, e.Bounds); if(selected) { e.Graphics.DrawString(this.GetItemText(e.Index), e.Font, selectFontBursh, e.Bounds); } else { e.Graphics.DrawString(this.GetItemText(e.Index), e.Font, normalFontBursh, e.Bounds); } e.DrawFocusRectangle(); base.OnDrawItem(e); }
值得一提的是這里判斷選中的不是用e.Index==this.SelectedIndex,而是用(e.State & DrawItemState.Selected) == DrawItemState.Selected,當ListBox的選擇模式用了多選而不是單選的時候,調用Select屬性會報錯,這個也是上面拖拽的局限性,當ListBox是多選的時候,上面的拖拽就會拋異常了,而且如果單純用e.Index==this.SelectIndex的話,選擇了一個元素,當在選擇另一個元素的時候,之前選擇過的元素的高亮狀態不會消失,這個是什么原因我也沒搞懂。如果有哪位園友知道的,麻煩指點一下。
最后附上整個控件的源碼
1 public class DragableListBox:ListBox 2 { 3 #region 字段 4 private bool isDraw; //是否執行繪制 5 SolidBrush evenRowBursh ; 6 SolidBrush oddRowBursh; 7 Brush normalFontBursh=SystemBrushes.ControlText; 8 Brush selectFontBursh = SystemBrushes.HighlightText; 9 Brush selectRowBursh = SystemBrushes.Highlight; 10 private bool dragAcross; 11 private bool dragSort; 12 13 public static ListBox dragSource; 14 #endregion 15 16 public DragableListBox() 17 { 18 this.DoubleBuffered = true; 19 this.OddColor = this.BackColor; 20 } 21 22 #region 外放成員 23 24 #region 屬性 25 26 [Description("跨ListBox拖放元素"), Category("行為")] 27 public bool DragAcross 28 { 29 get { return (dragAcross&&AllowDrop&&SelectionMode== System.Windows.Forms.SelectionMode.One); } 30 set 31 { 32 dragAcross = value; 33 if (value) this.AllowDrop = true; 34 } 35 } 36 37 [Description("元素拖動排序"),Category("行為")] 38 public bool DragSort 39 { 40 get { return dragSort && AllowDrop && SelectionMode == System.Windows.Forms.SelectionMode.One; } 41 set 42 { 43 dragSort = value; 44 if (value) this.AllowDrop = true; 45 } 46 } 47 48 private Color oddColor; 49 [Description("單數行的底色"), Category("外觀")] 50 public Color OddColor 51 { 52 get { return oddColor; } 53 set 54 { 55 oddColor = value; 56 isDraw = oddColor != this.BackColor; 57 if (isDraw) DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; 58 else DrawMode = System.Windows.Forms.DrawMode.Normal; 59 } 60 } 61 62 #endregion 63 64 #region 事件 65 66 [Description("跨ListBox拖拽完成后觸發"),Category("行為")] 67 public event DraggdHandler DraggedAcross; 68 69 [Description("拖拽排序后觸發"), Category("行為")] 70 public event DraggdHandler DraggedSort; 71 72 #endregion 73 74 #endregion 75 76 #region 重寫方法 77 78 #region 拖拽 79 80 protected override void OnDragDrop(DragEventArgs drgevent) 81 { 82 base.OnDragDrop(drgevent); 83 if (!DragAcross && !DragSort) return; 84 85 object item = dragSource.SelectedItem; 86 87 if (DragAcross && !DragSort && dragSource != this) 88 { 89 dragSource.Items.Remove(item); 90 this.Items.Add(item); 91 if (DraggedAcross != null) 92 DraggedAcross(this, new DraggedEventArgs() { DragItem=item, SourceControl=dragSource }); 93 } 94 else if (DragSort &&(( dragSource == this&&! DragAcross)||DragAcross)) 95 { 96 int index = this.IndexFromPoint(this.PointToClient(new Point(drgevent.X, drgevent.Y))); 97 dragSource.Items.Remove(item); 98 if (index < 0) 99 this.Items.Add(item); 100 else 101 this.Items.Insert(index, item); 102 if (DragAcross && DraggedAcross != null) 103 DraggedAcross(this, new DraggedEventArgs() { DragItem=item,SourceControl=dragSource }); 104 if (DraggedSort != null) 105 DraggedSort(this, new DraggedEventArgs() { DragItem=item,SourceControl=dragSource, DestineIndex=index }); 106 } 107 108 } 109 110 protected override void OnDragOver(DragEventArgs drgevent) 111 { 112 base.OnDragOver(drgevent); 113 if (!DragAcross&&!DragSort) return; 114 115 //dragDestince=this; 116 drgevent.Effect = DragDropEffects.Move; 117 } 118 119 protected override void OnMouseDown(MouseEventArgs e) 120 { 121 base.OnMouseDoubleClick(e); 122 if (!DragAcross && !DragSort ) return; 123 124 if (this.Items.Count == 0 || e.Button != MouseButtons.Left || this.SelectedIndex == -1 || e.Clicks == 2) 125 return; 126 dragSource = this; 127 128 int index = this.SelectedIndex; 129 object item = this.Items[index]; 130 DragDropEffects dde = DoDragDrop(item, 131 DragDropEffects.All); 132 } 133 134 #endregion 135 136 #region 繪制 137 138 protected override void OnDrawItem(DrawItemEventArgs e) 139 { 140 if (this.Items.Count < 0) return; 141 if (!isDraw) return; 142 if (e.Index < 0) return; 143 bool selected = (e.State & DrawItemState.Selected) == DrawItemState.Selected; 144 if(selected) 145 //if (e.Index == this.SelectedIndex) 146 e.Graphics.FillRectangle(selectRowBursh, e.Bounds); 147 else if (e.Index % 2 != 0) 148 e.Graphics.FillRectangle(OddRowBursh, e.Bounds); 149 else 150 e.Graphics.FillRectangle(EvenRowBursh, e.Bounds); 151 152 //if (e.Index == this.SelectedIndex ) 153 if(selected) 154 { 155 e.Graphics.DrawString(this.GetItemText(e.Index), e.Font, 156 selectFontBursh, e.Bounds); 157 158 } 159 else 160 { 161 e.Graphics.DrawString(this.GetItemText(e.Index), e.Font, 162 normalFontBursh, e.Bounds); 163 } 164 e.DrawFocusRectangle(); 165 base.OnDrawItem(e); 166 } 167 168 protected override void Dispose(bool disposing) 169 { 170 base.Dispose(disposing); 171 if (oddRowBursh != null) oddRowBursh.Dispose(); 172 if (evenRowBursh != null) evenRowBursh.Dispose(); 173 } 174 175 #endregion 176 177 #endregion 178 179 #region 私有方法和屬性 180 181 private SolidBrush EvenRowBursh 182 { 183 get 184 { 185 if(evenRowBursh==null) 186 { 187 evenRowBursh = new SolidBrush(this.BackColor); 188 return evenRowBursh; 189 } 190 if ( evenRowBursh.Color == this.BackColor)return evenRowBursh; 191 evenRowBursh.Dispose(); 192 evenRowBursh = new SolidBrush(this.BackColor); 193 return evenRowBursh; 194 } 195 set 196 { 197 if(evenRowBursh!=null) evenRowBursh.Dispose(); 198 evenRowBursh = value; 199 } 200 } 201 202 private SolidBrush OddRowBursh 203 { 204 get 205 { 206 if (oddRowBursh == null) 207 { 208 oddRowBursh = new SolidBrush(this.OddColor); 209 return oddRowBursh; 210 } 211 if (oddRowBursh.Color == this.OddColor) return oddRowBursh; 212 oddRowBursh.Dispose(); 213 oddRowBursh = new SolidBrush(this.OddColor); 214 return oddRowBursh; 215 } 216 set 217 { 218 if (oddRowBursh != null) oddRowBursh.Dispose(); 219 oddRowBursh = value; 220 } 221 } 222 223 private string GetItemText(int index) 224 { 225 try 226 { 227 object item = this.Items[index]; 228 if (string.IsNullOrEmpty(this.DisplayMember) || string.IsNullOrWhiteSpace(this.DisplayMember)) 229 return item.ToString(); 230 PropertyInfo proInfo = item.GetType().GetProperty(this.DisplayMember); 231 return proInfo.GetValue(item, null).ToString(); 232 } 233 catch { return this.Name; } 234 } 235 236 #endregion 237 238 public class DraggedEventArgs:EventArgs 239 { 240 public ListBox SourceControl { get; set; } 241 242 public object DragItem { get; set; } 243 244 public int DestineIndex { get; set; } 245 246 public DraggedEventArgs() 247 { 248 DestineIndex = -1; 249 } 250 } 251 252 public delegate void DraggdHandler(object sender, DraggedEventArgs e); 253 }
