之前在写播放器的时候,遇到了一个问题,现在播放器无论是千千,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 else101 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 else150 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 else160 {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 #endregion176 177 #endregion178 179 #region 私有方法和属性180 181 private SolidBrush EvenRowBursh182 {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 OddRowBursh203 {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 try226 {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 #endregion237 238 public class DraggedEventArgs:EventArgs239 {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 }