The PropertyGrid control is convenient for adding object editing support with minimal code. However, for some reason it does not support property navigation with the tab key. Instead, clicking tab key will progressively set focus to other controls in your form. This post provides a simple subclassed PropertyGrid with support for tab-key navigation of properties.
Below is the source code to a subclassed PropertyGrid control called TabbedPropertyGrid. There were two major issues that needed to be overcome. The first is that the PropertyGrid does not raise any keyboard events, to workaround this the subclassed PropertyGrid must hijack keyboard events from the parent form. The second issue is that there is no intuitive way to navigate GridItems. However, this MSDN forum thread provided a few clues to solve this using the Griditems and Parent property.
public class TabbedPropertyGrid : PropertyGrid { public TabbedPropertyGrid() : base() { } public void SetParent(Form form) { // Catch null arguments if (form == null) { throw new ArgumentNullException("form"); } // Set this property to intercept all events form.KeyPreview = true; // Listen for keydown event form.KeyDown += new KeyEventHandler(this.Form_KeyDown); } private void Form_KeyDown(object sender, KeyEventArgs e) { // Exit if cursor not in control if (!this.RectangleToScreen(this.ClientRectangle).Contains(Cursor.Position)) { return; } // Handle tab key if (e.KeyCode != Keys.Tab) { return; } e.Handled = true; e.SuppressKeyPress = true; // Get selected griditem GridItem gridItem = this.SelectedGridItem; if (gridItem == null) { return; } // Create a collection all visible child griditems in propertygrid GridItem root = gridItem; while (root.GridItemType != GridItemType.Root) { root = root.Parent; } List<GridItem> gridItems = new List<GridItem>(); this.FindItems(root, gridItems); // Get position of selected griditem in collection int index = gridItems.IndexOf(gridItem); // Select next griditem in collection this.SelectedGridItem = gridItems[++index]; } private void FindItems(GridItem item, List<GridItem> gridItems) { switch (item.GridItemType) { case GridItemType.Root: case GridItemType.Category: foreach (GridItem i in item.GridItems) { this.FindItems(i, gridItems); } break; case GridItemType.Property: gridItems.Add(item); if (item.Expanded) { foreach (GridItem i in item.GridItems) { this.FindItems(i, gridItems); } } break; case GridItemType.ArrayValue: break; } } }
After adding the TabbedPropertyGrid to your form, the form must be parsed into the control using the SetParent method.
public partial class Form1 : Form { public Form1() { InitializeComponent(); // Assign the form to the propertygrid this.tabbedPropertyGrid1.SetParent(this); this.tabbedPropertyGrid1.SelectedObject = this; } }
Known Issues: Shift-Tab does not select properties in reverse order.
instead of checking whether cursor is inside (why??) think better to check ContainsFocus property
ReplyDeleteThe line of code:
ReplyDeletethis.SelectedGridItem = gridItems[++index];
will overstep the bounds of the gridItems array if you tab past the last grid item.
I changed it to:
int nextIndex = index + 1;
if (nextIndex >= gridItems.Count){return;}
// Select next griditem in collection
this.SelectedGridItem = gridItems[nextIndex];
Has anyone been able to implement the Shift-Tab key to navigate backwards?
ReplyDeleteThis Property Grid control is able to display the properties of any object in a user friendly way and allows the end users of your applications edit the properties of the object.
ReplyDeleteI couldn't get Shift+Tab to work for reasons mentioned below. But I came up with a compromise. If someone else solves it I'll be really grateful.
ReplyDeleteThe other part of my story is that I also wanted the cursor to be in the value cell, not the property name cell. The original code puts focus on the property name, not the value.
If you forget about wanting the cursor in the value cell, you can get Tab and Shift+Tab to step forwards and backwards between property names. with the following code:
if ((Control.ModifierKeys & Keys.Shift) == 0)
{
int nextIndex = index + 1;
if (nextIndex >= gridItems.Count) { return; }
this.SelectedGridItem = gridItems[nextIndex];
}
else
{
int prevIndex = index - 1;
if (prevIndex < 0) { return; }
this.SelectedGridItem = gridItems[prevIndex];
}
The 'gotcha' is that you have to press Tab twice to move to another property (once for the property and once for the value). Shift+Tab is another whole world of hurt as after pressing Shift+Tab you have to press Tab again to enter the value cell before pressing another Shift+Tab. Otherwise the focus steps out of the PropertyGrid. You cannot press successive Shift+Tabs.
Of course this wasn't what I was after.
My solution to getting into the value cell, with one Tab, was to send another Tab using 'SendKey.Send("{Tab}");' straight after the next grid item had been set as in the following code for the 'Tab' key:
if ((Control.ModifierKeys & Keys.Shift) != 0)
{
int nextIndex = index + 1;
if (nextIndex >= gridItems.Count) { return; }
this.SelectedGridItem = gridItems[nextIndex];
SendKeys.Send("{Tab}");
}
This is highly counter-intuitive as you'd expect it to move to the next property name field as it would get captured by this code again. Anyway it worked.
However this doesn't work for Shift+Tab handling.
The 'Shift' key is still being held down when the additional Tab key is sent.
If you attempted a single Shift+Tab without the extra SendKey Tab, and then pressed Tab you'd end up in the value cell for the property name you'd Shift+Tabbed(?) to. Have I lost anyone yet? Good.
The best I could do was force the Tab to cycle through the property grid starting from the top again when the last property was reached. I've kept the 'Control.Modifiers & Keys.Shift' if ... because... I could.
if ((Control.ModifierKeys & Keys.Shift) == 0)
{
int nextIndex = index + 1;
if (nextIndex >= gridItems.Count)
this.SelectedGridItem = gridItems[0]; // Valid Alternative : gridItems[0].Select();
else
this.SelectedGridItem = gridItems[nextIndex]; // Valid Alternative : gridItems[nextIndex].Select();
SendKeys.Send("{Tab}");
}
Thanks for the sharing this code. Is the code above (if ((Control.ModifierKeys & Keys.Shift) == 0)) supposed to be in the keydown event? If you could share where you placed this snippet for the extra tab press using SendKeys, that would be great. I realize this was published years ago, so I will be lucky to get an answer :)
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteThanks for sharing the code, but can you provide step wise clarification of the code please.
ReplyDelete