- Download CustomDataGridView(src and demo) - 24.7 KB
- Note: Better effect when browsing with FireFox
Figure 1. Click down arrow to last row or wheel to first row.
Introduction
We all know that the CurrentRow does not move with VerticalScrollBar scrolling or mouse wheeling in DataGridView, ie. we click or drag vertical scrollbar or wheeling mouse, the CurrentRow does no change and may be invisible. Here we introduce a method to solve this problem in custom DataGridView control.
Key Points
The key points is to override one method and two events in custom DataGridView:
- Override WndProc() to capture mouse click at two ends of vertical scrollbar
- Override OnScroll event to process vertial scrollbar scrolling
- Override OnMouseWheel event to process mouse wheeling
1) Override WndProc() method
The first key point is that we must override message process method WndProc() to capture the click at two ends of vertical scrollbar when it is visible.We capture the vertical scrolling message WM_VSCROLL(0x115), and judge its direction by m.WParam:
- The up arrow is clicked on vertical scrollbar if m.WParam = SB_LINEUP(ie. m.WParam = 0)
- The down arrow is clicked on vertical scrollbar if m.WParam = SB_LINEDOWN(ie. m.WParam = 1)
We can see the first row if this.Rows[0].Displayed is true, and will move up one row when we click the up arrow on vertical scrollbar.Likely, we can see the last row if this.Rows[this.Rows.Count-1].Displayed is true, we will move down one row when we click the down arrow on vertical scrollbar.
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_VSCROLL) // vscroll message
{
int wParam = m.WParam.ToInt32();
if (wParam == SB_LINEUP) // SB_LINEUP: click the uparrow on vscrollbar
{
if (this.Rows[0].Displayed) // first row displayed
{
this.MoveCurrentRowWithMouseWheel(1);
}
}
else if (wParam == SB_LINEDOWN) // click the downarrow on vscrollbar
{
if (this.Rows[this.Rows.Count - 1].Displayed)
{
this.MoveCurrentRowWithMouseWheel(-1);
}
}
}
}
2) Override OnMouseWheel() event
If DataGridView is focused, the OnScoll event will be fired when we wheel mouse and FirstDisplayedScrolllingRowIndex changes, which we process it in OnScroll().If first displayed row does not change, we can get the wheeling direction by MouseEventArgs, and then move to correct CurrentRow.
protected override void OnMouseWheel(MouseEventArgs e)
{
int preFirstRowIndex = this.FirstDisplayedScrollingRowIndex;
base.OnMouseWheel(e);
if (preFirstRowIndex == this.FirstDisplayedScrollingRowIndex)
{
// delta > 0 wheel up; delta < 0 wheel down
this.MoveCurrentRowWithMouseWheel(e.Delta);
}
}
3) Override OnScroll() event
This is the core point to move to CurrentRow, since OnMouseWheel event and vertical scrollbar click both need to process it.We can get the correct CurrentRow easily, but we must point out that multiple OnScroll events will be invocated when we set new CurrentRow and scroll down!The class member m_HandlingOnScrollEvent is used just to prevent multiple invocations.
protected override void OnScroll(ScrollEventArgs e)
{
// OnScroll is invocated by resetting this.CurrentCell
if (m_HandlingOnScrollEvent)
{
return;
}
int preRowIndex;
if (this.CurrentRow == null)
{
preRowIndex = this.FirstDisplayedScrollingRowIndex;
}
else
{
preRowIndex = this.CurrentRow.Index;
}
base.OnScroll(e);
if (e.ScrollOrientation != ScrollOrientation.VerticalScroll)
{
return;
}
int curRowIndex;
if (this.CurrentRow == null)
{
curRowIndex = this.FirstDisplayedScrollingRowIndex;
}
else
{
curRowIndex = this.CurrentRow.Index;
}
int curColIndex = 0;
if (this.CurrentCell == null)
{
curColIndex = this.CurrentCell.ColumnIndex;
}
int lastRowIndex = this.FirstDisplayedScrollingRowIndex + this.DisplayedRowCount(true)-1;
// before the first displayed row
if (curRowIndex < this.FirstDisplayedScrollingRowIndex)
{
curRowIndex = this.FirstDisplayedScrollingRowIndex;
}
else if (curRowIndex > lastRowIndex) // after the last displayed row
{
curRowIndex = lastRowIndex;
}
// reset this.CurrentCell may invocate OnScroll event several times
m_HandlingOnScrollEvent = true;
this.CurrentCell = this.Rows[curRowIndex].Cells[curColIndex];
m_HandlingOnScrollEvent = false;
}
4) Move to new CurrentRow
The OnScroll event will not be fired when first or last displayed row does change and we click two ends of vertical scrollbar or wheel mouse.So, we must process these cases by method below.
private void MoveCurrentRowWithMouseWheel(int direction)
{
if (this.Rows.Count <= 1) // consider only Rows.Count >= 2
{
return;
}
int curRowIndex = 0;
if (this.CurrentRow != null)
{
curRowIndex = this.CurrentRow.Index;
}
int curColIndex = 0;
if (this.CurrentCell != null)
{
curColIndex = this.CurrentCell.ColumnIndex;
}
int lastRowIndex = this.FirstDisplayedScrollingRowIndex + this.DisplayedRowCount(true) - 1;
if (direction > 0) // scroll up
{
curRowIndex--;
if (curRowIndex < this.FirstDisplayedScrollingRowIndex)
{
curRowIndex = this.FirstDisplayedScrollingRowIndex;
}
}
else // scroll down
{
curRowIndex++;
if (curRowIndex > lastRowIndex)
{
curRowIndex = lastRowIndex;
}
}
this.CurrentCell = this.Rows[curRowIndex].Cells[curColIndex];
}
Using the Code
After unzipping file, we can click doubly the file CustomDataGridViewDemo.sln to view the demo if we have VS2005/2008, or we can run CustomDataGridViewDemo.exe in subfolder /bin/ to test the control.
In the zip file, we can see the CustomDataGridView.cs code below, which customs a DataGridView control.We can drag this control from ToolBar of VS2005/2008 to forms if we compile it successfully.
class CustomDataGridView: DataGridView
{
private const int WM_VSCROLL = 0x0115; // VScrollBar scroll message
private const int SB_LINEUP = 0; // click the up arrow of the VScrollBar
private const int SB_LINEDOWN = 1; // click the down arrow the VScrollBar
private bool m_HandlingOnScrollEvent = false;
public CustomDataGridView() { }
// WndProc() method
// OnMouseWheel() event
// OnScroll() event
// MoveCurrentRowWithMouseWheel() method
}
Conclusion
Two difficult problems I encounter list below:
- How to capture the click event at two ends of vertical scrollbar, since VerticalScrollBar property of DataGridView has CaptuerMouseChanged event only and we can not judge the move direction from it
- Multiple invocations in OnScroll when setting new CurrentRow
The skills described above has been applied to my TDataGridViewEx control which is inherited from DataGridView and has the features:
- Can show multiple lines on grid header
- Can show aggregate rows
- Can update aggregate row values when data updated in DataSource
- Can set cell or row BackColor
- Can show row or column index
We can see TDataGridViewEx design introduction or
download and test it(only exe demo) .