Discussion:
No Tab Stop DataGridView Column
(too old to reply)
Richard MSL
2007-04-19 21:20:04 UTC
Permalink
I have a DataGridView control with some regular columns, and some columns
that are just for display, populated by my program. The user is not supposed
to be able to change the data in these columns, or even tab or mouse to them.
I can set the column ReadOnly property, but the tab and mouse still allow
that column to be selected.

Is there a way to make the column just display, without being able to tab or
mouse to it? When the user tabbed from the column on the left, I want it to
just skip over the display column, directly to the next editable column.
Thanks.
Linda Liu [MSFT]
2007-04-20 12:42:05 UTC
Permalink
Hi Richard,

To stop navigating to a readonly cell in a DataGridView via Tab, we could
derive a new class from DataGridView and override the ProcessDialogKey and
ProcessDataGridViewKey methods in the subclass.

When the current cell in a DataGridView is in edit mode, and the user press
a key, the override ProcessDialogKey method is called. When the current
cell is not in edit mode, and the user press a key, the override
ProcessDataGridViewKey method is called. So these two methods compensate
each other.

In the overrid ProcessDialogKey and ProcessDataGridViewKey methods, seek
the next editable cell and if find then set it as the current cell.

The following is a sample.

class MyDataGridView:DataGridView
{
protected override bool ProcessDialogKey(Keys keyData)
{
Keys key = (keyData & Keys.KeyCode);

if (key == Keys.Tab)
{
int col = this.CurrentCell.ColumnIndex + 1;
for (; col < this.Columns.Count; col++)
{
if (!this.Columns[col].ReadOnly)
{ break; }
}
if (col < this.Columns.Count)
{
this.CurrentCell =
this.Rows[this.CurrentCell.RowIndex].Cells[col];
}
else
{
if (this.CurrentCell.RowIndex != this.Rows.Count - 1)
{
for (col = 0; col <= this.CurrentCell.ColumnIndex;
col++)
{
if (!this.Columns[col].ReadOnly)
{
break;
}
}
if (col <= this.CurrentCell.ColumnIndex)
{
this.CurrentCell =
this.Rows[this.CurrentCell.RowIndex + 1].Cells[col];
}
}
}
return true;

}
return base.ProcessDialogKey(keyData);
}
protected override bool ProcessDataGridViewKey(KeyEventArgs e)
{
if (e.KeyData == Keys.Tab)
{
int col = this.CurrentCell.ColumnIndex +1;
for (; col < this.Columns.Count; col++)
{
if (!this.Columns[col].ReadOnly)
{ break; }
}
if (col < this.Columns.Count)
{
this.CurrentCell =
this.Rows[this.CurrentCell.RowIndex].Cells[col];
}
else
{
if (this.CurrentCell.RowIndex != this.Rows.Count - 1)
{
for (col = 0; col <= this.CurrentCell.ColumnIndex;
col++)
{
if (!this.Columns[col].ReadOnly)
{
break;
}
}
if (col <= this.CurrentCell.ColumnIndex)
{
this.CurrentCell =
this.Rows[this.CurrentCell.RowIndex + 1].Cells[col];
}
}
}
return true;
}
return base.ProcessDataGridViewKey(e);
}
}

Use the derived DataGridView on your form and you should see the readonly
cells don't get selected when you navigate through the DataGridView using
TAB key.

As for your second question of prevent the readonly cells from being
selected via mouse click, I will go on research and will get back to you
ASAP. I appreciate your patience.


Sincerely,
Linda Liu
Microsoft Online Community Support

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
Larry Smith
2007-04-20 12:40:50 UTC
Permalink
Post by Richard MSL
I have a DataGridView control with some regular columns, and some columns
that are just for display, populated by my program. The user is not supposed
to be able to change the data in these columns, or even tab or mouse to them.
I can set the column ReadOnly property, but the tab and mouse still allow
that column to be selected.
Is there a way to make the column just display, without being able to tab or
mouse to it? When the user tabbed from the column on the left, I want it to
just skip over the display column, directly to the next editable column.
Thanks.
You would think that such a common requirement would be natively available
but it's not. You have to roll it yourself and it's not trivial. For one
thing, the grid is buggy and will often result in various errors if you try
to change the target cell from within different "DataGridView" event
handlers (when you trying to modifyc the "CurrentCell" property typically).
The following is the most common:

"Operation is not valid because it results in a reentrant call to the
SetCurrentCellAddressCore function"

I saw at least one posting elsewhere where Mark Rideout confirms this
problem under some another circumstance. He's the guy who manages this
control at MSFT. The only way I've been able to find to circumvent this
problem (after much pain) was to provide a "DataGridView" derivative. You
then have to override "SetCurrentCellAddressCore()" and
"SetSelectedCellCore()" and change the "columnIndex" parameter (and/or
"rowIndex") before invoking the base class version. You may also have to
implement an "OnKeyDown()" override as well. For instance, in my case, the
left-hand column is always off-limits to the user. If they click it then the
first two functions mentioned above will automatically move them to the
adjacent cell on the right. If they then <Shift-Tab> from the latter column
however then my "OnKeyDown()" override traps it and sets "CurrentCell" to
the right-most column on the previous visible row (assuming they're not on
the first visible row in the grid at the time - make sure you're handling
*visible* rows when you do this, assuming your grid contains hidden rows
either now or in the future). You'll have to experiment to get everything
right of course and cope with the frustration along the way. Good luck.
Richard MSL
2007-04-20 15:02:03 UTC
Permalink
Thanks Linda and Larry for the detailed responses. I had thought that my
requirement was common and trivial, and that I was somehow overlooking the
obvious solution, but I see that this is not the case. At least now I know
what I am dealing with, thanks again.
Post by Larry Smith
Post by Richard MSL
I have a DataGridView control with some regular columns, and some columns
that are just for display, populated by my program. The user is not supposed
to be able to change the data in these columns, or even tab or mouse to them.
I can set the column ReadOnly property, but the tab and mouse still allow
that column to be selected.
Is there a way to make the column just display, without being able to tab or
mouse to it? When the user tabbed from the column on the left, I want it to
just skip over the display column, directly to the next editable column.
Thanks.
You would think that such a common requirement would be natively available
but it's not. You have to roll it yourself and it's not trivial. For one
thing, the grid is buggy and will often result in various errors if you try
to change the target cell from within different "DataGridView" event
handlers (when you trying to modifyc the "CurrentCell" property typically).
"Operation is not valid because it results in a reentrant call to the
SetCurrentCellAddressCore function"
I saw at least one posting elsewhere where Mark Rideout confirms this
problem under some another circumstance. He's the guy who manages this
control at MSFT. The only way I've been able to find to circumvent this
problem (after much pain) was to provide a "DataGridView" derivative. You
then have to override "SetCurrentCellAddressCore()" and
"SetSelectedCellCore()" and change the "columnIndex" parameter (and/or
"rowIndex") before invoking the base class version. You may also have to
implement an "OnKeyDown()" override as well. For instance, in my case, the
left-hand column is always off-limits to the user. If they click it then the
first two functions mentioned above will automatically move them to the
adjacent cell on the right. If they then <Shift-Tab> from the latter column
however then my "OnKeyDown()" override traps it and sets "CurrentCell" to
the right-most column on the previous visible row (assuming they're not on
the first visible row in the grid at the time - make sure you're handling
*visible* rows when you do this, assuming your grid contains hidden rows
either now or in the future). You'll have to experiment to get everything
right of course and cope with the frustration along the way. Good luck.
Linda Liu [MSFT]
2007-04-23 10:39:03 UTC
Permalink
Hi Richard,

To prevent the user from selecting the readonly cells via mouse, we could
override the WndProc method in the derived DataGridView class to catch the
WM_LBUTTONDOWN and WM_LBUTTONDBLCLK messages. If the cell to be selected is
readonly, discard the Windows message, so that the cell won't be selected;
otherwise let the message go.

The following is a sample.

class MyDataGridView:DataGridView
{
int WM_LBUTTONDOWN = 0x0201;
int WM_LBUTTONDBLCLK = 0x0203;
int MK_LBUTTON = 0x1;

protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONDOWN || m.Msg == WM_LBUTTONDBLCLK)
{
if (m.WParam.ToInt32() == MK_LBUTTON)
{
int lparam = m.LParam.ToInt32();
int xpos = lparam & 0x0000FFFF;
int ypos = lparam >> 16;
if (!IsReadonlyCell(xpos, ypos))
{
base.WndProc(ref m);
}
}
}
else
{
base.WndProc(ref m);
}

}

private bool IsReadonlyCell(int xpos, int ypos)
{
int column = 0;
for (; column < this.ColumnCount; column++)
{
if (this.GetColumnDisplayRectangle(column,
true).Contains(xpos, ypos))
{
break;
}
}

if (column < this.ColumnCount)
{
if (this.Columns[column].ReadOnly)
return true;
else
return false;
}
else
{
return false;
}
}
}

Hope this helps.
If you have any question, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support
nltoft
2009-04-24 23:15:01 UTC
Permalink
I just spent a couple of hours trying to work out the same things as Richard.
Glad I found this thread so I'd stop wasting my time. I would have never
come up with these solutions.

I have a DataGridView with the first column read only and the second
column for editting. I wanted tab to skip the first column. I managed
to remove the selection on the cell in the first column and set it on
the second column in the grid's CellEnter(), but discovered the re-entry
problem when I tried to set the CurrentCell.

Would it be possible to inject a tab programatically from CellEnter()
(when the column index is 0 in my case) to force the focus to move
to the next column?

Thanks.
Post by Linda Liu [MSFT]
Hi Richard,
To prevent the user from selecting the readonly cells via mouse, we could
override the WndProc method in the derived DataGridView class to catch the
WM_LBUTTONDOWN and WM_LBUTTONDBLCLK messages. If the cell to be selected is
readonly, discard the Windows message, so that the cell won't be selected;
otherwise let the message go.
The following is a sample.
class MyDataGridView:DataGridView
{
int WM_LBUTTONDOWN = 0x0201;
int WM_LBUTTONDBLCLK = 0x0203;
int MK_LBUTTON = 0x1;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONDOWN || m.Msg == WM_LBUTTONDBLCLK)
{
if (m.WParam.ToInt32() == MK_LBUTTON)
{
int lparam = m.LParam.ToInt32();
int xpos = lparam & 0x0000FFFF;
int ypos = lparam >> 16;
if (!IsReadonlyCell(xpos, ypos))
{
base.WndProc(ref m);
}
}
}
else
{
base.WndProc(ref m);
}
}
private bool IsReadonlyCell(int xpos, int ypos)
{
int column = 0;
for (; column < this.ColumnCount; column++)
{
if (this.GetColumnDisplayRectangle(column,
true).Contains(xpos, ypos))
{
break;
}
}
if (column < this.ColumnCount)
{
if (this.Columns[column].ReadOnly)
return true;
else
return false;
}
else
{
return false;
}
}
}
Hope this helps.
If you have any question, please feel free to let me know.
Sincerely,
Linda Liu
Microsoft Online Community Support
Loading...