如何在拖放过程中自动滚动DataGridView

我的C#.NET应用程序中的一个表单有多个DataGridViews,它们实现拖放以移动行。 拖放大多数工作正常,但我一直很难将DataGridViews转换为AutoScroll – 当在行的顶部或底部附近拖动一行时,向该方向滚动它。

到目前为止,我已经尝试实现此解决方案的一个版本。 我有一个inheritance自DataGridView的ScrollingGridView类,它实现了所描述的计时器,并且根据调试器,计时器正在适当地触发,但是计时器代码:

const int WM_VSCROLL = 277; private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam); private void ScrollingGridViewTimerTick(object sender, EventArgs e) { SendMessage(Handle, WM_VSCROLL, (IntPtr)scrollDirectionInt, IntPtr.Zero); } 

据我所知,我没有做任何事情,可能是因为我在表单中有多个DataGridViews。 我也尝试修改AutoScrollOffset属性,但这也没有做任何事情。 对DataGridView和ScrollBar类的研究似乎没有提出实际使DataGridView滚动的任何其他命令或函数。 任何人都可以帮助我实际滚动DataGridView的function,或其他一些方法来解决问题?

我有一段时间没有看过这段代码。 但不久前我实现了一个支持这个的DataGridView。

  class DragOrderedDataGridView : System.Windows.Forms.DataGridView { public delegate void RowDroppedEventHangler(object source, DataGridViewRow sourceRow, DataGridViewRow destinationRow); public event RowDroppedEventHangler RowDropped; bool bDragging = false; System.Windows.Forms.DataGridView.HitTestInfo hti = null; System.Threading.Timer scrollTimer = null; delegate void SetScrollDelegate(int value); public bool AllowDragOrdering { get; set; } protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) { if (AllowDragOrdering) { DataGridView.HitTestInfo hti = this.HitTest(eX, eY); if (hti.RowIndex != -1 && hti.RowIndex != this.NewRowIndex && e.Button == MouseButtons.Left) { bDragging = true; } } base.OnMouseDown(e); } protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e) { if (bDragging && e.Button == MouseButtons.Left) { DataGridView.HitTestInfo newhti = this.HitTest(eX, eY); if (hti != null && hti.RowIndex != newhti.RowIndex) { System.Diagnostics.Debug.WriteLine("invalidating " + hti.RowIndex.ToString()); Invalidate(); } hti = newhti; System.Diagnostics.Debug.WriteLine(string.Format("{0:000} {1} ", hti.RowIndex, e.Location)); Point clientPoint = this.PointToClient(e.Location); System.Diagnostics.Debug.WriteLine(e.Location + " " + this.Bounds.Size); if (scrollTimer == null && ShouldScrollDown(e.Location)) { // // enable the timer to scroll the screen // scrollTimer = new System.Threading.Timer(new System.Threading.TimerCallback(TimerScroll), 1, 0, 250); } if (scrollTimer == null && ShouldScrollUp(e.Location)) { scrollTimer = new System.Threading.Timer(new System.Threading.TimerCallback(TimerScroll), -1, 0, 250); } } else { bDragging = false; } if (!(ShouldScrollUp(e.Location) || ShouldScrollDown(e.Location))) { StopAutoScrolling(); } base.OnMouseMove(e); } bool ShouldScrollUp(Point location) { return location.Y > this.ColumnHeadersHeight && location.Y < this.ColumnHeadersHeight + 15 && location.X >= 0 && location.X <= this.Bounds.Width; } bool ShouldScrollDown(Point location) { return location.Y > this.Bounds.Height - 15 && location.Y < this.Bounds.Height && location.X >= 0 && location.X <= this.Bounds.Width; } void StopAutoScrolling() { if (scrollTimer != null) { // // disable the timer to scroll the screen // scrollTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); scrollTimer = null; } } void TimerScroll(object state) { SetScrollBar((int)state); } bool scrolling = false; void SetScrollBar(int direction) { if (scrolling) { return; } if (this.InvokeRequired) { this.Invoke(new Action(SetScrollBar), new object[] {direction}); } else { scrolling = true; if (0 < direction) { if (this.FirstDisplayedScrollingRowIndex < this.Rows.Count - 1) { this.FirstDisplayedScrollingRowIndex++; } } else { if (this.FirstDisplayedScrollingRowIndex > 0) { this.FirstDisplayedScrollingRowIndex--; } } scrolling = false; } } protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e) { bDragging = false; HitTestInfo livehti = hti; hti = null; if (RowDropped != null && livehti != null && livehti.RowIndex != -1 && this.CurrentRow.Index != livehti.RowIndex) { RowDropped(this, this.CurrentRow, this.Rows[livehti.RowIndex]); } StopAutoScrolling(); Invalidate(); base.OnMouseUp(e); } protected override void OnCellPainting(System.Windows.Forms.DataGridViewCellPaintingEventArgs e) { if (bDragging && hti != null && hti.RowIndex != -1 && e.RowIndex == hti.RowIndex) { // // draw the indicator // Pen p = new Pen(Color.FromArgb(0, 0, 215)); p.Width = 4; e.Graphics.DrawLine(p, e.CellBounds.Left, e.CellBounds.Top, e.CellBounds.Right, e.CellBounds.Top); } base.OnCellPainting(e); } } 

感谢您对我的问题的帮助,也许我可以帮助您。

从这个页面:

 [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam); //winuser.h constants private const int WM_VSCROLL = 277; // Vertical scroll private const int SB_LINEUP = 0; // Scrolls one line up private const int SB_LINEDOWN = 1; // Scrolls one line down private const int SB_ENDSCROLL = 8; // Ends the scrolling //Call this when you want to scroll private void ScrollGridview(int direction) { SendMessage(Handle, WM_VSCROLL, (IntPtr)direction, VerticalScrollBar.Handle); SendMessage(Handle, WM_VSCROLL, (IntPtr)SB_ENDSCROLL, VerticalScrollBar.Handle); } 

(第二个SendMessage似乎没有必要,但我把它包括在内以获得良好的衡量标准)

我写了一个DataGridView控件,它包含了这个 gbogumil的自动滚动解决方案和一个function正常的OnPaint突出显示 – 你可以在这里找到它。

我还要指出这个控件 ,我刚刚从另一个线程中找到了。 它看起来非常好,但遗憾的是它是GPL,所以你只能将它用于GPL项目。 不过,它完成了我们需要的所有东西以及更多function。

 private void TargetReasonGrid_DragOver(object sender, DragEventArgs e) { e.Effect = DragDropEffects.Move; //Converts window position to user control position (otherwise you can use MousePosition.Y) int mousepos = PointToClient(Cursor.Position).Y; //If the mouse is hovering over the bottom 5% of the grid if (mousepos > (TargetReasonGrid.Location.Y + (TargetReasonGrid.Height * 0.95))) { //If the first row displayed isn't the last row in the grid if (TargetReasonGrid.FirstDisplayedScrollingRowIndex < TargetReasonGrid.RowCount - 1) { //Increase the first row displayed index by 1 (scroll down 1 row) TargetReasonGrid.FirstDisplayedScrollingRowIndex = TargetReasonGrid.FirstDisplayedScrollingRowIndex + 1; } } //If the mouse is hovering over the top 5% of the grid if (mousepos < (TargetReasonGrid.Location.Y + (TargetReasonGrid.Height * 0.05))) { //If the first row displayed isn't the first row in the grid if (TargetReasonGrid.FirstDisplayedScrollingRowIndex > 0) { //Decrease the first row displayed index by 1 (scroll up 1 row) TargetReasonGrid.FirstDisplayedScrollingRowIndex = TargetReasonGrid.FirstDisplayedScrollingRowIndex - 1; } } } 

这里有很多有用的答案,我想我会为这个问题添加一个不那么复杂的解决方案。 在DataGridView中拖动行时,将调用上面的代码。 我的名字叫“TargetReasonGrid”。

我添加了解释我正在做什么的笔记,但这里是拼出的步骤:

  1. 转换鼠标位置,使其相对于网格位置(在窗体/控件中)

  2. 在显示的网格边缘设置虚拟区域,鼠标移动将触发滚动

  3. 检查以确保实际上有不同的行可以滚动到

  4. 以1行为增量滚动

感谢C4u(上面评论),给了我“想象区域”的想法。