2010-03-19 107 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 
    } 
}