2010-03-19 29 views
6

Người dùng kéo các hàng lên và xuống trong DataGridView của tôi. Tôi có logic kéo xuống, nhưng tôi muốn có một điểm đánh dấu tối cho biết hàng sẽ được đặt sau khi tôi thả chuột ra.Dấu trực quan khi di chuyển các hàng trên DataGridView

Example from Microsoft Access http://img718.imageshack.us/img718/8171/accessdrag.png
Ví dụ từ Microsoft Access; Tôi muốn kéo các hàng thay vì cột

Có ai biết tôi sẽ làm như thế nào không? Được xây dựng trong này, hoặc tôi sẽ phải vẽ điểm đánh dấu của riêng tôi (nếu có, làm thế nào để tôi làm điều đó)?

Cảm ơn!

+0

Điều này đang được thực hiện i n WPF? (Tôi phải thừa nhận, nó trông giống như WPF từ ảnh chụp màn hình, nhưng tôi vẫn không quen thuộc với WPF chưa ...) – Pretzel

+0

Không có nó WinForms; Ảnh chụp màn hình đó là Access 2007, cũng là (tôi tin) không WPF –

+0

Điều thú vị là cả hai thứ tự cột và dấu trực quan đều được xây dựng. –

Trả lời

3

Tôi đã làm điều này cho một lần xem trước một vài năm trước; không thể nhớ chính xác như thế nào, nhưng hãy cân nhắc sử dụng sự kiện MouseMove của DataGridView.

Trong khi kéo đang xảy ra, xử lý MouseMove của bạn nên:

  • có được tọa độ tương đối của các chuột (các MouseEventArgs chứa tọa độ, nhưng tôi nghĩ rằng họ đang tọa độ màn hình, vì vậy bạn có thể sử dụng DataGridView.PointToClient() để chuyển đổi chúng thành tương đối)
  • xác định hàng nào ở vị trí X (có phương pháp này không? Nếu không, bạn có thể tính toán nó bằng cách thêm hàng + chiều cao tiêu đề hàng, nhưng hãy nhớ rằng lưới có thể đã được cuộn)
  • làm nổi bật hàng đó hoặc làm tối biên giới của nó. Một cách bạn có thể làm tối một đường viền là thay đổi thuộc tính DataGridViewRow.DividerHeight.
  • khi chuột di chuyển ra ngoài hàng đó hàng, hãy khôi phục nó về cách trước đây được xem.

Nếu bạn muốn làm điều gì đó tùy chỉnh với giao diện của hàng dưới chuột (thay vì chỉ sử dụng các thuộc tính có sẵn), bạn có thể sử dụng sự kiện DataGridView.RowPostPaint. Nếu bạn triển khai trình xử lý cho sự kiện này chỉ được sử dụng khi một hàng đang được kéo qua một hàng khác, bạn có thể vẽ lại đường viền trên cùng hoặc dưới cùng của hàng bằng một bàn chải mạnh hơn. MSDN example here.

+0

Có một phương thức để lấy hàng/cột, đó là 'DataGridView. HitTest() '. Tuy nhiên, trừ khi tôi có thể tối hơn chỉ một cạnh của đường viền, điều này không cho tôi biết gì mới: hàng được chèn sẽ xuất hiện ** giữa ** hai hàng hiện tại, thay vì thay thế một hàng, vì vậy tôi muốn một đường tối giữa hai hàng (xem ví dụ ở trên). Tôi có thể làm gì khi tôi có hình chữ nhật hiển thị của hàng? –

+0

Quên hình chữ nhật, tôi có một ý tưởng tốt hơn: tạo một trình xử lý cho sự kiện DataGridView.RowPostPaint. Khi chuột qua hàng, kích hoạt trình xử lý này. Trong trình xử lý sự kiện, hãy vẽ lại đường viền dưới cùng (hoặc trên cùng, tùy thuộc vào nơi thả xuống) với một bàn chải nặng hơn. (Tôi sẽ cập nhật câu trả lời của tôi) Nhưng trước khi bạn thử điều đó, bạn có thể chơi với thuộc tính DataGridViewRow.DividerHeight, là đường viền dưới cùng của hàng. Nếu bạn tạm thời tăng gấp đôi chiều cao của đường viền, nó có thể cung cấp cho bạn tác động trực quan mà bạn đang tìm kiếm. –

+0

DividerHeight hoạt động khá tốt, ngay bây giờ. Tôi sẽ phải xem xét RowPostPaint sau, khi tôi có nhiều thời gian hơn. Cảm ơn! –

1

Ứng dụng mà tôi đang làm trên điểm đánh dấu là đối tượng Panel riêng biệt với chiều cao là 1 và BackColor của 1. Đối tượng Panel được giữ ẩn cho đến khi kéo và thả thực sự đang được tiến hành. Chức năng này, kích hoạt trên các sự kiện DragOver, thực hiện hầu hết các logic:

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

Khác hơn thế, bạn chỉ cần có để ẩn Bảng điều chỉnh một lần nữa khi kéo và thả là hoàn chỉnh hoặc di chuyển ra khỏi DataGridView.

+0

Thật không may, điều này không làm việc - lơ lửng trên bảng điều khiển kích hoạt sự kiện DragLeave! (Ngoài ra, nếu chúng xảy ra để lơ lửng trên bảng điều khiển khi chúng thả chuột, kéo thả sẽ không xảy ra) –

+1

Chỉ cần nhìn vào nó trên ứng dụng của tôi. Hóa ra sự kiện DragLeave không được kích hoạt khi bạn vượt qua bảng điều khiển, nhưng trong mã của tôi, tất cả những gì DragLeave làm là ẩn bảng điều khiển, sau đó làm cho kéo nhập DataGridView trở lại và cuộc gọi HitTest trong DragOver sau đó di chuyển bảng điều khiển lên một lần nữa. – Mason

3

Đây là giải pháp cuối cùng của tôi.kiểm soát này:

  • Cho phép kéo một hàng khác
  • nổi bật vị trí chèn sử dụng một chia
  • Tự động cuộn khi người dùng được cho các cạnh của sự kiểm soát trong khi kéo
  • Hỗ trợ nhiều phiên bản kiểm soát
    • có thể kéo các hàng từ một ví dụ khác
    • Chỉ một hàng sẽ được lựa chọn trong suốt tất cả các trường hợp điều khiển
  • Tuỳ chỉnh làm nổi bật hàng

Bạn có thể làm bất cứ điều gì bạn muốn với mã này (không có bảo hành, v.v.)

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 
    } 
} 
Các vấn đề liên quan