波斯马BOSSMA Information Technology

DragListView – 实现在ListView控件中拖拽(拖动)列表项(行)

发布时间:2011年10月13日 / 分类:WinForm / 18,101 次浏览 / 评论

很久没有写过WinForm的程序了,这几天整理了下以前写过的几个控件,发现一些比较有用的扩展。今天介绍一个自定义控件DragListView,可以拖拽其中的行,移动它们的位置,从而重新排序。

这个控件适用于使用Details视图显示项的情况。

原理:继承ListView,当点击选中一个列表项时,拖动鼠标时,释放鼠标时,重写相关拖拽方法,实现拖动效果,将选中的列表项插入到拖拽位置。拖动鼠标的过程中,通过判断拖动的数据和位置来设置拖拽的效果。

 

1、重写如下几个关于拖拽的方法:

 

OnItemDrag:启动拖拽,设置拖拽的数据和效果。

OnDragEnter:拖拽进入ListView,判断拖拽的数据格式,并设置拖拽的效果。

OnDragOver:拖动经过ListView时,设置拖动的效果,显示拖放位置线。

OnDragDrop:拖拽释放,移动行。

为了判断拖动的方向,还需要重写OnMouseDown,获取鼠标按下时的坐标,和拖拽释放时的坐标进行比较,判断出向上或向下。

 

2、为了直接拖出来控件就能使用,这里重写了几个属性,设置了默认值。

 

AllowDrop:指示控件是否可以接受用户拖到它上面的数据。默认为True。

FullRowSelect:指示当项被选中时,其所有子项是否同该项一起突出显示。默认为True。

GridLines:指示是否在项和子项周围显示网格线,仅在“详细信息”视图中显示。默认为True。

View:选择可以显示项的五种不同视图中的一种。默认为Details。

Sorting:指示对项进行排序的方式,默认为None才不会自动排序。

 

看看效果:

(1)拖拽中:

(2)拖拽释放:

 

下边看看代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;

namespace VeryCodes.Windows.Forms
{
    public class DragAndDropListView : ListView
    {
        #region 私有变量
        /// <summary>
        /// 上一个拖动悬停的项
        /// </summary>
        private ListViewItem lastHoverItem;

        /// <summary>
        /// 指示是否允许拖动行重新排序
        /// </summary>
        private bool allowDragRowReorder;

        /// <summary>
        /// 上一次鼠标按下时Y轴坐标,用于判断拖动上下方向
        /// </summary>
        private int lastMouseDownY = -1;

        /// <summary>
        /// 上一次鼠标按下时X轴坐标,用于判断拖动左右方向
        /// </summary>
        private int lastMouseDownX = -1;

        /// <summary>
        /// 拖拽位置线的颜色
        /// </summary>
        private Color lineColor;
        #endregion

        #region 公有属性
        /// <summary>
        /// 获取或设置一个值,该值指示是否允许拖动行重新排序。
        /// </summary>
        [Description("指示是否允许拖动行重新排序。")]
        [Category("DragListView")]
        [DefaultValue(true)]
        public bool AllowDragRowReorder
        {
            get
            {
                return this.allowDragRowReorder;
            }

            set
            {
                this.allowDragRowReorder = value;
            }
        }

        /// <summary>
        /// 获取或设置一个值,该值指示控件是否可以接受用户拖到它上面的数据。
        /// </summary>
        [Description("指示控件是否可以接受用户拖到它上面的数据。")]
        [Category("DragListView")]
        [DefaultValue(true)]
        public new bool AllowDrop
        {
            get
            {
                return base.AllowDrop;
            }

            set
            {
                base.AllowDrop = value;
            }
        }

        /// <summary>
        /// 获取或设置一个值,该值指示当项被选中时,其所有子项是否同该项一起突出显示。
        /// </summary>
        [Description("指示当项被选中时,其所有子项是否同该项一起突出显示。")]
        [Category("DragListView")]
        [DefaultValue(true)]
        public new bool FullRowSelect
        {
            get
            {
                return base.FullRowSelect;
            }

            set
            {
                base.FullRowSelect = value;
            }
        }

        /// <summary>
        /// 获取或设置一个值,该值指示是否在项和子项周围显示网格线,仅在“详细信息”视图中显示。
        /// </summary>
        [Description("指示是否在项和子项周围显示网格线,仅在“详细信息”视图中显示")]
        [Category("DragListView")]
        [DefaultValue(true)]
        public new bool GridLines
        {
            get
            {
                return base.GridLines;
            }

            set
            {
                base.GridLines = value;
            }
        }

        /// <summary>
        /// 获取或设置项在控件中的显示方式。
        /// </summary>
        [Description("选择可以显示项的五种不同视图中的一种。")]
        [Category("DragListView")]
        [DefaultValue(View.Details)]
        public new View View
        {
            get
            {
                return base.View;
            }

            set
            {
                base.View = value;
            }
        }

        /// <summary>
        /// 重新设计Sorting属性,原有的属性如果不为None,则不能拖动行变化位置。
        /// </summary>
        [Description("指示对项进行排序的方式。")]
        [Category("DragListView")]
        [DefaultValue(View.Details)]
        public new SortOrder Sorting
        {
            get
            {
                return SortOrder.None;
            }

            set
            {
                base.Sorting = SortOrder.None;
            }
        }

        /// <summary>
        /// 获取或设置拖拽位置线的颜色。
        /// </summary>
        [Description("获取或设置拖拽位置线的颜色。")]
        [Category("DragListView")]
        [DefaultValue(View.Details)]
        public Color LineColor
        {
            get { return lineColor; }
            set { lineColor = value; }
        }
        #endregion

        /// <summary>
        /// 初始化DragAndDropListView类的一个新实例
        /// </summary>
        public DragAndDropListView() : base()
        {
            this.AllowDragRowReorder = true;
            this.AllowDrop = true;
            this.FullRowSelect = true;
            this.GridLines = true;
            this.View = View.Details;

            lineColor = Color.Red;
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        }

        #region 重写方法
        /// <summary>
        /// 当拖拽释放时的处理
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDragDrop(DragEventArgs e)
        {
            // 不允许拖拽排序,则直接调用重写的基类方法,然后返回。
            if (!allowDragRowReorder)
            {
                base.OnDragDrop(e);
                return;
            }

            //判断拖拽的数据是否正确
            if (!e.Data.GetDataPresent(typeof(DragItemData).ToString()) || ((DragItemData)e.Data.GetData(typeof(DragItemData).ToString())).ListView == null || ((DragItemData)e.Data.GetData(typeof(DragItemData).ToString())).DragItems.Count == 0)
            {
                return;
            }

            // 获取拖拽的数据
            DragItemData data = (DragItemData)e.Data.GetData(typeof(DragItemData).ToString());

            // 获取最终悬停的项
            Point cp = base.PointToClient(new Point(e.X, e.Y));
            ListViewItem dragToItem = base.GetItemAt(cp.X, cp.Y);

            // 如果没有悬停项,则添加到最后
            if (dragToItem == null)
            {
                for (int i = 0; i < data.DragItems.Count; i++)
                {
                    ListViewItem newItem = (ListViewItem)data.DragItems[i];
                    base.Items.Add(newItem);
                }
            }
            else
            {
                // 如果是向下拖拽,则设置拖动的行的Index为拖动到的行的Index+1
                int dropIndex = dragToItem.Index;
                if (this.View == View.Details || this.View == View.List)
                {
                    if (lastMouseDownY > cp.Y) // 向上
                    {
                    }
                    else // 向下
                    {
                        dropIndex++;
                    }
                }
                else
                {
                    if (lastMouseDownX > cp.X) // 向左
                    {
                    }
                    else //向右
                    {
                        dropIndex++;
                    }
                }

                // 插入拖动的数据到指定的位置
                for (int i = data.DragItems.Count - 1; i >= 0; i--)
                {
                    ListViewItem newItem = (ListViewItem)data.DragItems[i];
                    base.Items.Insert(dropIndex, newItem);
                }
            }

            // 从拖出ListView中移除数据
            if (data.ListView != null)
            {
                foreach (ListViewItem itemToRemove in data.ListView.SelectedItems)
                {
                    data.ListView.Items.Remove(itemToRemove);
                }
            }

            // set the back color of the previous item, then nullify it
            if (lastHoverItem != null)
            {
                lastHoverItem = null;
            }

            this.Invalidate();

            // call the base on drag drop to raise the event
            base.OnDragDrop(e);
        }

        /// <summary>
        /// 当拖拽经过项时的处理
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDragOver(DragEventArgs e)
        {
            // 不允许拖拽排序,则直接调用重写的基类方法,然后返回。
            if (!allowDragRowReorder)
            {
                base.OnDragOver(e);
                return;
            }

            // 如果被拖拽的数据和预期的数据类型无关联,则不处理,否则设置拖拽效果。
            if (!e.Data.GetDataPresent(typeof(DragItemData).ToString()))
            {
                e.Effect = DragDropEffects.None;
                base.OnDragOver(e);
                return;
            }

            if (this.Items.Count > 0)
            {
                // 获取拖拽当前悬停的项
                Point cp = this.PointToClient(new Point(e.X, e.Y));
                ListViewItem hoverItem = this.GetItemAt(cp.X, cp.Y);

                // 如果鼠标移动到项的外边,但是在ListView区域内,X轴移动
                if (hoverItem == null)
                {
                    if (cp.X < this.Location.X + this.Width && cp.X > this.Location.X)
                    {
                        hoverItem = base.GetItemAt(this.Location.X + 20, cp.Y);
                    }
                }

                // 获取当前控件 GDI+ 绘图图面
                Graphics g = this.CreateGraphics();

                // 如果没有就拖到最后一个
                if (hoverItem == null)
                {
                    e.Effect = DragDropEffects.Move;

                    if (lastHoverItem != null)
                    {
                        lastHoverItem = null;
                        this.Invalidate();
                    }

                    hoverItem = this.Items[this.Items.Count - 1];
                    DrawHoverLine(cp, hoverItem, g);

                    base.OnDragOver(e);
                    return;
                }

                // 判断拖拽当前是否悬停在一个新的项上,如果是则重绘控件
                if (lastHoverItem == null || lastHoverItem != hoverItem)
                {
                    this.Invalidate();
                }

                // 如果是同一行,则不执行返回
                foreach (ListViewItem itemToMove in this.SelectedItems)
                {
                    if (itemToMove.Index == hoverItem.Index)
                    {
                        e.Effect = DragDropEffects.None;
                        //hoverItem.EnsureVisible();
                        this.Invalidate();
                        base.OnDragOver(e);
                        return;
                    }
                }

                // 绘制拖动放置线
                DrawHoverLine(cp, hoverItem, g);

                lastHoverItem = hoverItem;

                // 确保悬浮行是可见的
                hoverItem.EnsureVisible();
            }

            // 设置拖动效果
            e.Effect = DragDropEffects.Move;

            // 调用重写的基类方法
            base.OnDragOver(e);
        }

        /// <summary>
        /// 当拖拽操作进入控件时的处理
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDragEnter(DragEventArgs e)
        {
            // 不允许拖拽排序,则直接调用重写的基类方法,然后返回。
            if (!allowDragRowReorder)
            {
                base.OnDragEnter(e);
                return;
            }

            // 如果被拖拽的数据和预期的数据类型无关联,则不处理,否则设置拖拽效果。
            if (!e.Data.GetDataPresent(typeof(DragItemData).ToString()))
            {
                e.Effect = DragDropEffects.None;
                return;
            }
            else
            {
                e.Effect = DragDropEffects.Move;
            }

            // 调用被重写的基类方法。
            base.OnDragEnter(e);
        }

        /// <summary>
        /// 当项被拖拽时的处理
        /// </summary>
        /// <param name="e"></param>
        protected override void OnItemDrag(ItemDragEventArgs e)
        {
            // 不允许拖拽排序,则直接调用重写的基类方法,然后返回。
            if (!allowDragRowReorder)
            {
                base.OnItemDrag(e);
                return;
            }

            // 触发拖放操作。
            base.DoDragDrop(GetDataForDragDrop(), DragDropEffects.Move);

            // 调用被重写的基类方法。
            base.OnItemDrag(e);
        }

        /// <summary>
        /// 当控件失去焦点时的处理
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLostFocus(EventArgs e)
        {
            // 重新设置选中项的背景,移除“上一个悬停项”的值
            this.ResetOutOfRange();

            // 重绘
            this.Invalidate();

            // 调用重写的基类方法
            base.OnLostFocus(e);
        }

        /// <summary>
        /// 当拖拽操作离开控件时的处理
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDragLeave(EventArgs e)
        {
            // 重新设置选中项的背景,移除“上一个悬停项”的值
            ResetOutOfRange();

            // 重绘
            this.Invalidate();

            // 调用重写的基类方法
            base.OnDragLeave(e);
        }

        /// <summary>
        /// 重写OnMouseDown,获取鼠标按下时的坐标
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            lastMouseDownY = e.Y;
            lastMouseDownX = e.X;

            base.OnMouseDown(e);
        }
        #endregion

        #region 私有方法
        /// <summary>
        /// 获取拖拽项
        /// </summary>
        /// <returns></returns>
        private DragItemData GetDataForDragDrop()
        {
            // 创建一个DragItemData类的实例,用于在拖拽过程中进行处理,如项位置变化等
            DragItemData data = new DragItemData(this);

            // 将选中项的副本保存到拖拽项集合中
            foreach (ListViewItem item in this.SelectedItems)
            {
                data.DragItems.Add(item.Clone());
            }

            return data;
        }

        /// <summary>
        ///
        /// </summary>
        private void ResetOutOfRange()
        {
            // determine if the previous item exists,
            // if it does, reset the background and release
            // the previous item
            if (lastHoverItem != null)
            {
                lastHoverItem = null;
            }
        }

        /// <summary>
        /// 绘画拖动位置线
        /// </summary>
        /// <param name="cp"></param>
        /// <param name="hoverItem"></param>
        /// <param name="g"></param>
        private void DrawHoverLine(Point cp, ListViewItem hoverItem, Graphics g)
        {
            if (this.View == View.Details || this.View == View.List)
            {
                if (lastMouseDownY > cp.Y) // 向上
                {
                    g.DrawLine(new Pen(lineColor, 2), new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X + this.Bounds.Width, hoverItem.Bounds.Y));
                    g.FillPolygon(new SolidBrush(lineColor), new Point[] { new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y - 5), new Point(hoverItem.Bounds.X + 5, hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y + 5) });
                    g.FillPolygon(new SolidBrush(lineColor), new Point[] { new Point(this.Bounds.Width - 4, hoverItem.Bounds.Y - 5), new Point(this.Bounds.Width - 9, hoverItem.Bounds.Y), new Point(this.Bounds.Width - 4, hoverItem.Bounds.Y + 5) });
                }
                else // 向下
                {
                    g.DrawLine(new Pen(Color.Red, 2), new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y + hoverItem.Bounds.Height), new Point(hoverItem.Bounds.X + this.Bounds.Width, hoverItem.Bounds.Y + hoverItem.Bounds.Height));
                    g.FillPolygon(new SolidBrush(Color.Red), new Point[] { new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y + hoverItem.Bounds.Height - 5), new Point(hoverItem.Bounds.X + 5, hoverItem.Bounds.Y + hoverItem.Bounds.Height), new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y + hoverItem.Bounds.Height + 5) });
                    g.FillPolygon(new SolidBrush(Color.Red), new Point[] { new Point(this.Bounds.Width - 4, hoverItem.Bounds.Y + hoverItem.Bounds.Height - 5), new Point(this.Bounds.Width - 9, hoverItem.Bounds.Y + hoverItem.Bounds.Height), new Point(this.Bounds.Width - 4, hoverItem.Bounds.Y + hoverItem.Bounds.Height + 5) });
                }
            }
            else
            {
                if (lastMouseDownX > cp.X) // 向左
                {
                    g.DrawLine(new Pen(lineColor, 2), new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y + hoverItem.Bounds.Height));
                    g.FillPolygon(new SolidBrush(lineColor), new Point[] { new Point(hoverItem.Bounds.X - 5, hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X + 5, hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y + 5) });
                    g.FillPolygon(new SolidBrush(lineColor), new Point[] { new Point(hoverItem.Bounds.X - 5, hoverItem.Bounds.Y + hoverItem.Bounds.Height), new Point(hoverItem.Bounds.X + 5, hoverItem.Bounds.Y + hoverItem.Bounds.Height), new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y + hoverItem.Bounds.Height - 5) });
                }
                else // 向右
                {
                    g.DrawLine(new Pen(lineColor, 2), new Point(hoverItem.Bounds.X + hoverItem.Bounds.Width, hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X + hoverItem.Bounds.Width, hoverItem.Bounds.Y + hoverItem.Bounds.Height));
                    g.FillPolygon(new SolidBrush(lineColor), new Point[] { new Point(hoverItem.Bounds.X + hoverItem.Bounds.Width - 5, hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X + hoverItem.Bounds.Width + 5, hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X + hoverItem.Bounds.Width, hoverItem.Bounds.Y + 5) });
                    g.FillPolygon(new SolidBrush(lineColor), new Point[] { new Point(hoverItem.Bounds.X + hoverItem.Bounds.Width - 5, hoverItem.Bounds.Y + hoverItem.Bounds.Height), new Point(hoverItem.Bounds.X + hoverItem.Bounds.Width + 5, hoverItem.Bounds.Y + hoverItem.Bounds.Height), new Point(hoverItem.Bounds.X + hoverItem.Bounds.Width, hoverItem.Bounds.Y + hoverItem.Bounds.Height - 5) });
                }
            }
        }
        #endregion

        #region DragItemData Class
        /// <summary>
        /// 用于封装拖拽项
        /// </summary>
        private class DragItemData
        {
            #region Private Members
            /// <summary>
            /// 所属的DragAndDropListView
            /// </summary>
            private DragAndDropListView m_listView;

            /// <summary>
            /// 拖拽项集合
            /// </summary>
            private ArrayList m_dragItems;
            #endregion

            #region Public Properties
            /// <summary>
            /// 获取所属的DragAndDropListView
            /// </summary>
            public DragAndDropListView ListView
            {
                get { return m_listView; }
            }

            /// <summary>
            /// 获取拖拽项集合
            /// </summary>
            public ArrayList DragItems
            {
                get { return m_dragItems; }
            }
            #endregion

            #region Public Methods and Implementation
            /// <summary>
            /// 初始化DragAndDropListView类的一个新实例
            /// </summary>
            /// <param name="listView"></param>
            public DragItemData(DragAndDropListView listView)
            {
                m_listView = listView;
                m_dragItems = new ArrayList();
            }
            #endregion
        }
        #endregion
    }
}

里边注释很详细了,如果还有不清楚的请留言。

本博客所有文章如无特别注明均为原创。
复制或转载请以超链接形式注明转自波斯马,原文地址《DragListView – 实现在ListView控件中拖拽(拖动)列表项(行)

关键字:

建议订阅本站,及时阅读最新文章!
【上一篇】 【下一篇】

发表评论