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

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

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

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

谢谢!

几年前我做了一个树视图; 不记得究竟是怎么回事,但考虑使用DataGridView的MouseMove事件。

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

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

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

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

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

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

 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 OverallSelectionChanged; private SolidBrush _dividerBrush; private Pen _selectionPen; #region Designer properties ///  /// The color of the divider displayed between rows while dragging ///  [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); } } ///  /// The color of the border drawn around the selected row ///  [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); } } ///  /// Height (in pixels) of the divider to display ///  [Browsable(true)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] [Category("Appearance")] [Description("Height (in pixels) of the divider to display")] [DefaultValue(4)] public int DividerHeight { get; set; } ///  /// Width (in pixels) of the border around the selected row ///  [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 ///  /// 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 ///  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(eX, eY)); //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? } } ///  /// Based on the mouse position, determines where the new row would /// be inserted if the user were to release the mouse-button right now ///  ///  /// The y-coordinate of the mouse, given with respectto the control /// (not the screen) ///  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 } } 

我正在处理的应用程序将标记作为单独的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。