2010-03-19 104 views
6

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

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

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

谢谢!

+0

这是正在做我WPF? (我不得不承认,从截图看起来像WPF,但我仍然不熟悉WPF ......) – Pretzel 2010-03-19 19:29:41

+0

不,它是WinForms;截图是Access 2007,这也是(我相信)不是WPF – 2010-03-19 19:31:52

+0

有趣的是,列排序和可视标记内置。 – 2010-03-19 21:47:39

回答

3

几年前,我为树视图做了这个工作;不能完全记得如何,但考虑使用DataGridView的MouseMove事件。

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

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

如果您想在鼠标下面的行的外观(而不是仅使用可用属性)自定义某些内容,则可以使用DataGridView.RowPostPaint事件。如果您为此事件实施处理程序,该处理程序仅在行被拖动到另一行时使用,则可以用大胆的笔刷重新绘制该行的顶部或底部边框。 MSDN example here.

+0

是的,有一种方法来获取行/列,它是'DataGridView。的HitTest()'。但是,除非我只能将边框的一边变暗,否则这并不会告诉我任何新内容:插入的行会在**两个当前行之间出现**,而不是替换一个,所以我需要在两个行(参见上面的示例)。一旦我有行的显示矩形?我能做些什么? – 2010-03-19 18:12:46

+0

忘记矩形,我有一个更好的主意:为DataGridView.RowPostPaint事件创建一个处理程序。当鼠标在该行上时,激活该处理程序。在事件处理程序中,用较重的刷子重新绘制底部边界(或顶部,取决于滴落点的位置)。 (我会更新我的答案) 但在你尝试之前,你可能会玩DataGridViewRow.DividerHeight属性,这是该行的底部边框。如果您暂时将边框的高度加倍,则可能会给您带来视觉上的影响。 – 2010-03-19 19:07:46

+0

现在DividerHeight工作得很好。稍后,当我有更多时间时,我将不得不查看RowPostPaint。谢谢! – 2010-03-19 20:44:58

1

我正在处理的应用程序将标记作为高度为1且BackColor为1的单独Panel对象执行。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的。

+0

不幸的是,这不起作用 - 悬停在面板上会触发DragLeave事件! (另外,如果它们碰巧放在面板上,当它们放开鼠标时,拖放将不会发生) – 2010-04-02 20:41:22

+1

只需在我的应用程序中查看它。事实证明,当你传递面板时,DragLeave事件会被触发,但在我的代码中,DragLeave所做的就是隐藏面板,然后再次拖动进入DataGridView,然后DragOver中的HitTest调用将面板移动再起。 – Mason 2010-04-02 21:11:07

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 
    } 
}