在DataGridView上移动行时的可视标记

时间:2010-03-19 17:18:01

标签: c# .net winforms datagridview drag-and-drop

用户在我的DataGridView中上下拖动行。我有拖动逻辑,但我希望有一个黑色标记,指示在放开鼠标后行将放置在哪里。

Example from Microsoft Access http://img718.imageshack.us/img718/8171/accessdrag.png
来自Microsoft Access的示例;我想拖动行而不是列

有谁知道我会怎么做?这是内置的,还是我必须绘制自己的标记(如果是这样,我该怎么做)?

谢谢!

3 个答案:

答案 0 :(得分:3)

几年前我在树视图中这样做了;不记得具体如何,但考虑使用DataGridView的MouseMove事件。

在发生拖动时,您的MouseMove处理程序应该:

  • 获取相对坐标 鼠标(MouseEventArgs包含 坐标,但我认为它们是屏幕坐标,因此您可以使用DataGridView.PointToClient()将它们转换为相对坐标。
  • 确定哪一行在该X处 position(有没有这个方法?如果没有,你可以通过加总行+行标题高度来计算它,但记住网格可能已经滚动了)
  • 突出显示该行或使其变暗 边界。一种可以使一个边框变暗的方法是更改​​DataGridViewRow.DividerHeight属性。
  • 当鼠标向外移动时 行,将其恢复到之前的状态 看去。

如果您想使用鼠标下方的行外观进行自定义操作(而不是仅使用可用属性),则可以使用DataGridView.RowPostPaint事件。如果为此事件实现处理程序,该处理程序仅在将行拖过另一行时使用,则可以使用更粗的画笔重新绘制行的顶部或底部边框。 MSDN example here.

答案 1 :(得分:3)

这是我最终的解决方案。这个控制:

  • 允许将一行拖到另一行
  • 使用分隔符突出显示插入位置
  • 当用户在拖动时到达控件边缘时自动滚动
  • 支持控件的多个实例
    • 可以将行从一个实例拖到另一个实例
    • 在控件的所有实例中仅选择一行
  • 自定义突出显示行

您可以使用此代码执行任何操作(无保修等)

using System;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace CAM_Products.General_Controls
{
    public class DataGridViewWithDraggableRows : DataGridView
    {
        private int? _predictedInsertIndex; //Index to draw divider at.  Null means no divider
        private Timer _autoScrollTimer;
        private int _scrollDirection;
        private static DataGridViewRow _selectedRow;
        private bool _ignoreSelectionChanged;
        private static event EventHandler<EventArgs> OverallSelectionChanged;
        private SolidBrush _dividerBrush;
        private Pen _selectionPen;

        #region Designer properties
        /// <summary>
        /// The color of the divider displayed between rows while dragging
        /// </summary>
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Category("Appearance")]
        [Description("The color of the divider displayed between rows while dragging")]
        public Color DividerColor
        {
            get { return _dividerBrush.Color; }
            set { _dividerBrush = new SolidBrush(value); }
        }

        /// <summary>
        /// The color of the border drawn around the selected row
        /// </summary>
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Category("Appearance")]
        [Description("The color of the border drawn around the selected row")]
        public Color SelectionColor
        {
            get { return _selectionPen.Color; }
            set { _selectionPen = new Pen(value); }
        }

        /// <summary>
        /// Height (in pixels) of the divider to display
        /// </summary>
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Category("Appearance")]
        [Description("Height (in pixels) of the divider to display")]
        [DefaultValue(4)]
        public int DividerHeight { get; set; }

        /// <summary>
        /// Width (in pixels) of the border around the selected row
        /// </summary>
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Category("Appearance")]
        [Description("Width (in pixels) of the border around the selected row")]
        [DefaultValue(3)]
        public int SelectionWidth { get; set; }
        #endregion

        #region Form setup
        public DataGridViewWithDraggableRows()
        {
            InitializeProperties();
            SetupTimer();
        }

        private void InitializeProperties()
        {
            #region Code stolen from designer
            this.AllowDrop = true;
            this.AllowUserToAddRows = false;
            this.AllowUserToDeleteRows = false;
            this.AllowUserToOrderColumns = true;
            this.AllowUserToResizeRows = false;
            this.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            this.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single;
            this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.EnableHeadersVisualStyles = false;
            this.MultiSelect = false;
            this.ReadOnly = true;
            this.RowHeadersVisible = false;
            this.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            this.CellMouseDown += dataGridView1_CellMouseDown;
            this.DragOver += dataGridView1_DragOver;
            this.DragLeave += dataGridView1_DragLeave;
            this.DragEnter += dataGridView1_DragEnter;
            this.Paint += dataGridView1_Paint_Selection;
            this.Paint += dataGridView1_Paint_RowDivider;
            this.DefaultCellStyleChanged += dataGridView1_DefaultcellStyleChanged;
            this.Scroll += dataGridView1_Scroll;
            #endregion

            _ignoreSelectionChanged = false;
            OverallSelectionChanged += OnOverallSelectionChanged;
            _dividerBrush = new SolidBrush(Color.Red);
            _selectionPen = new Pen(Color.Blue);
            DividerHeight = 4;
            SelectionWidth = 3;
        }
        #endregion

        #region Selection
        /// <summary>
        /// All instances of this class share an event, so that only one row
        /// can be selected throughout all instances.
        /// This method is called when a row is selected on any DataGridView
        /// </summary>
        private void OnOverallSelectionChanged(object sender, EventArgs e)
        {
            if(sender != this && SelectedRows.Count != 0)
            {
                ClearSelection();
                Invalidate();
            }
        }

        protected override void OnSelectionChanged(EventArgs e)
        {
            if(_ignoreSelectionChanged)
                return;

            if(SelectedRows.Count != 1 || SelectedRows[0] != _selectedRow)
            {
                _ignoreSelectionChanged = true; //Following lines cause event to be raised again
                if(_selectedRow == null || _selectedRow.DataGridView != this)
                {
                    ClearSelection();
                }
                else
                {
                    _selectedRow.Selected = true; //Deny new selection
                    if(OverallSelectionChanged != null)
                        OverallSelectionChanged(this, EventArgs.Empty);
                }
                _ignoreSelectionChanged = false;
            }
            else
            {
                base.OnSelectionChanged(e);
                if(OverallSelectionChanged != null)
                    OverallSelectionChanged(this, EventArgs.Empty);
            }
        }

        public void SelectRow(int rowIndex)
        {
            _selectedRow = Rows[rowIndex];
            _selectedRow.Selected = true;
            Invalidate();
        }
        #endregion

        #region Selection highlighting
        private void dataGridView1_Paint_Selection(object sender, PaintEventArgs e)
        {
            if(_selectedRow == null || _selectedRow.DataGridView != this)
                return;

            Rectangle displayRect = GetRowDisplayRectangle(_selectedRow.Index, false);
            if(displayRect.Height == 0)
                return;

            _selectionPen.Width = SelectionWidth;
            int heightAdjust = (int)Math.Ceiling((float)SelectionWidth/2);
            e.Graphics.DrawRectangle(_selectionPen, displayRect.X - 1, displayRect.Y - heightAdjust,
                                     displayRect.Width, displayRect.Height + SelectionWidth - 1);
        }

        private void dataGridView1_DefaultcellStyleChanged(object sender, EventArgs e)
        {
            DefaultCellStyle.SelectionBackColor = DefaultCellStyle.BackColor;
            DefaultCellStyle.SelectionForeColor = DefaultCellStyle.ForeColor;
        }

        private void dataGridView1_Scroll(object sender, ScrollEventArgs e)
        {
            Invalidate();
        }
        #endregion

        #region Drag-and-drop
        protected override void OnDragDrop(DragEventArgs args)
        {
            if(args.Effect == DragDropEffects.None)
                return;

            //Convert to coordinates within client (instead of screen-coordinates)
            Point clientPoint = PointToClient(new Point(args.X, args.Y));

            //Get index of row to insert into
            DataGridViewRow dragFromRow = (DataGridViewRow)args.Data.GetData(typeof(DataGridViewRow));
            int newRowIndex = GetNewRowIndex(clientPoint.Y);

            //Adjust index if both rows belong to same DataGridView, due to removal of row
            if(dragFromRow.DataGridView == this && dragFromRow.Index < newRowIndex)
            {
                newRowIndex--;
            }

            //Clean up
            RemoveHighlighting();
            _autoScrollTimer.Enabled = false;

            //Only go through the trouble if we're actually moving the row
            if(dragFromRow.DataGridView != this || newRowIndex != dragFromRow.Index)
            {
                //Insert the row
                MoveDraggedRow(dragFromRow, newRowIndex);

                //Let everyone know the selection has changed
                SelectRow(newRowIndex);
            }
            base.OnDragDrop(args);
        }

        private void dataGridView1_DragLeave(object sender, EventArgs e1)
        {
            RemoveHighlighting();
            _autoScrollTimer.Enabled = false;
        }

        private void dataGridView1_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = (e.Data.GetDataPresent(typeof(DataGridViewRow))
                            ? DragDropEffects.Move
                            : DragDropEffects.None);
        }

        private void dataGridView1_DragOver(object sender, DragEventArgs e)
        {
            if(e.Effect == DragDropEffects.None)
                return;

            Point clientPoint = PointToClient(new Point(e.X, e.Y));

            //Note: For some reason, HitTest is failing when clientPoint.Y = dataGridView1.Height-1.
            // I have no idea why.
            // clientPoint.Y is always 0 <= clientPoint.Y < dataGridView1.Height
            if(clientPoint.Y < Height - 1)
            {
                int newRowIndex = GetNewRowIndex(clientPoint.Y);
                HighlightInsertPosition(newRowIndex);
                StartAutoscrollTimer(e);
            }
        }

        private void dataGridView1_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
        {
            if(e.Button == MouseButtons.Left && e.RowIndex >= 0)
            {
                SelectRow(e.RowIndex);
                var dragObject = Rows[e.RowIndex];
                DoDragDrop(dragObject, DragDropEffects.Move);
                //TODO: Any way to make this *not* happen if they only click?
            }
        }

        /// <summary>
        /// Based on the mouse position, determines where the new row would
        /// be inserted if the user were to release the mouse-button right now
        /// </summary>
        /// <param name="clientY">
        /// The y-coordinate of the mouse, given with respectto the control
        /// (not the screen)
        /// </param>
        private int GetNewRowIndex(int clientY)
        {
            int lastRowIndex = Rows.Count - 1;

            //DataGridView has no cells
            if(Rows.Count == 0)
                return 0;

            //Dragged above the DataGridView
            if(clientY < GetRowDisplayRectangle(0, true).Top)
                return 0;

            //Dragged below the DataGridView
            int bottom = GetRowDisplayRectangle(lastRowIndex, true).Bottom;
            if(bottom > 0 && clientY >= bottom)
                return lastRowIndex + 1;

            //Dragged onto one of the cells.  Depending on where in cell,
            // insert before or after row.
            var hittest = HitTest(2, clientY); //Don't care about X coordinate

            if(hittest.RowIndex == -1)
            {
                //This should only happen when midway scrolled down the page,
                //and user drags over header-columns
                //Grab the index of the current top (displayed) row
                return FirstDisplayedScrollingRowIndex;
            }

            //If we are hovering over the upper-quarter of the row, place above;
            // otherwise below.  Experimenting shows that placing above at 1/4 
            //works better than at 1/2 or always below
            if(clientY < GetRowDisplayRectangle(hittest.RowIndex, false).Top
               + Rows[hittest.RowIndex].Height/4)
                return hittest.RowIndex;
            return hittest.RowIndex + 1;
        }

        private void MoveDraggedRow(DataGridViewRow dragFromRow, int newRowIndex)
        {
            dragFromRow.DataGridView.Rows.Remove(dragFromRow);
            Rows.Insert(newRowIndex, dragFromRow);
        }
        #endregion

        #region Drop-and-drop highlighting
        //Draw the actual row-divider
        private void dataGridView1_Paint_RowDivider(object sender, PaintEventArgs e)
        {
            if(_predictedInsertIndex != null)
            {
                e.Graphics.FillRectangle(_dividerBrush, GetHighlightRectangle());
            }
        }

        private Rectangle GetHighlightRectangle()
        {
            int width = DisplayRectangle.Width - 2;

            int relativeY = (_predictedInsertIndex > 0
                                 ? GetRowDisplayRectangle((int)_predictedInsertIndex - 1, false).Bottom
                                 : Columns[0].HeaderCell.Size.Height);

            if(relativeY == 0)
                relativeY = GetRowDisplayRectangle(FirstDisplayedScrollingRowIndex, true).Top;
            int locationX = Location.X + 1;
            int locationY = relativeY - (int)Math.Ceiling((double)DividerHeight/2);
            return new Rectangle(locationX, locationY, width, DividerHeight);
        }

        private void HighlightInsertPosition(int rowIndex)
        {
            if(_predictedInsertIndex == rowIndex)
                return;

            Rectangle oldRect = GetHighlightRectangle();
            _predictedInsertIndex = rowIndex;
            Rectangle newRect = GetHighlightRectangle();

            Invalidate(oldRect);
            Invalidate(newRect);
        }

        private void RemoveHighlighting()
        {
            if(_predictedInsertIndex != null)
            {
                Rectangle oldRect = GetHighlightRectangle();
                _predictedInsertIndex = null;
                Invalidate(oldRect);
            }
            else
            {
                Invalidate();
            }
        }
        #endregion

        #region Autoscroll
        private void SetupTimer()
        {
            _autoScrollTimer = new Timer
            {
                Interval = 250,
                Enabled = false
            };
            _autoScrollTimer.Tick += OnAutoscrollTimerTick;
        }

        private void StartAutoscrollTimer(DragEventArgs args)
        {
            Point position = PointToClient(new Point(args.X, args.Y));

            if(position.Y <= Font.Height/2 &&
               FirstDisplayedScrollingRowIndex > 0)
            {
                //Near top, scroll up
                _scrollDirection = -1;
                _autoScrollTimer.Enabled = true;
            }
            else if(position.Y >= ClientSize.Height - Font.Height/2 &&
                    FirstDisplayedScrollingRowIndex < Rows.Count - 1)
            {
                //Near bottom, scroll down
                _scrollDirection = 1;
                _autoScrollTimer.Enabled = true;
            }
            else
            {
                _autoScrollTimer.Enabled = false;
            }
        }

        private void OnAutoscrollTimerTick(object sender, EventArgs e)
        {
            //Scroll up/down
            FirstDisplayedScrollingRowIndex += _scrollDirection;
        }
        #endregion
    }
}

答案 2 :(得分:1)

我正在处理的应用程序将标记作为单独的Pan​​el对象,其高度为1,BackColor为1.Panel对象保持隐藏状态,直到拖放实际进行为止。在DragOver事件上触发的此函数实现了大部分逻辑:

public static void frameG_dragover(Form current_form, DataGridView FRAMEG, Panel drag_row_indicator, Point mousePos)
    {
        int FRAMEG_Row_Height = FRAMEG.RowTemplate.Height;
        int FRAMEG_Height = FRAMEG.Height;
        int Loc_X = FRAMEG.Location.X + 2;

        Point clientPoint = FRAMEG.PointToClient(mousePos);
        int CurRow = FRAMEG.HitTest(clientPoint.X, clientPoint.Y).RowIndex;
        int Loc_Y = 0;
        if (CurRow != -1)
        {
            Loc_Y = FRAMEG.Location.Y + ((FRAMEG.Rows[CurRow].Index + 1) * FRAMEG_Row_Height) - FRAMEG.VerticalScrollingOffset;
        }
        else
        {
            Loc_Y = FRAMEG.Location.Y + (FRAMEG.Rows.Count + 1) * FRAMEG_Row_Height;
        }

        int width_c = FRAMEG.Columns[0].Width + FRAMEG.Columns[1].Width + FRAMEG.Columns[2].Width;

        if ((Loc_Y > (FRAMEG.Location.Y)) && (Loc_Y < (FRAMEG.Location.Y + FRAMEG_Height - FRAMEG_Row_Height))) //+ FRAMEG_Row_Height
        {
            drag_row_indicator.Location = new System.Drawing.Point(Loc_X, Loc_Y);
            drag_row_indicator.Size = new Size(width_c, 1);
        }

        if (!drag_row_indicator.Visible)
            drag_row_indicator.Visible = true;
    }

除此之外,您只需在拖放完成或移出DataGridView后再次隐藏Panel。