J
Jeff Johnson
[VS 2005, Framework 2.0 (duh)]
I just learned about a wonderful thing called the Command Pattern and how it
can simplify adding undo/redo capabilities to an app, so I immediately set
about implementing it. It's working well. Due to the design of my app,
though, I'm having an issue with keyboard shortcuts. Here are the details:
My app manages a data file. This file contains one or more "blocks," and
each block contains one or more "instructions." The UI is set up with a list
view to manage the blocks, a grid to manage the instructions, and a set of
controls (text boxes, sliders, and a couple of check boxes) to actually
manipulate the data in the active row of the grid. (I'm not allowing in-cell
editing on the grid.)
I want to provide the standard Ctrl+Z/Y shortcuts for undo/redo (plus the
CUA Alt+Backspace, since we should all still be supporting the CUA keys,
right folks?), but here's where the problem arises: I don't include edits
you make in the text boxes in the list of things that can be undone. The
text boxes are just a "scratch area" and nothing is actually changed until
you hit the Apply button. Then your entire edit is recorded as an undoable
action. However, I want users to be able to use the built-in undo
functionality that the text box exposes (because experienced users will
probably expect that) so that, for example, if you accidentally highlight a
number and type over it, then realize you wanted the original number (but
don't know what it was since it was a big ugly decimal) then you can hit
Ctrl+Z and rescue yourself without having to hit the Reset button and lose
ALL your changes to that row.
Okay, with those as the requirements, the trick is to capture Ctrl+Z and NOT
activate the menu item Undo (which would undo the last "real" action) and
instead let Ctrl+Z be handled by the text box...IF the text box has anything
to undo. If it doesn't, then assume the user meant to really undo the last
action and do so (cancelling the edit in the process). It's sort of like
using MS Access and editing a row. If you've made changes to a cell, hiting
Escape once undoes that cell, and hitting it a second time (or if the cell
hasn't been modified) cancels changes to the row.
A simple method would be to simply not set Ctrl+Z/Y as menu shortcuts and
handle them purely through code, but I think it's pretty cheesy not to have
those shortcuts display on the Edit menu, especially since it's showing
Ctrl+X/C/V etc. So I guess ultimately my problem is aesthetics! (In VB6 I
would have set the caption of the menu to be <caption><tab><shortcut> and
the shortcut text would be DISPLAYED on the right-hand side of the menu as
if it were a real shortcut. I haven't tried it in WinForms but I don't think
it's the ideal solution in the first place.)
I'm using the 2.0 MenuStrip and not the older Menu controls, if that helps
you give me suggestions. What I think I need to do is find a way to handle
command keys before the MenuStrip does, and I'm currently reading about the
ProcessCmdKey event, but I figured I'd post here in case anyone has done
something similar and has advice. Hopefully the answer will involve
"hooking" and not loads of derived classes....
I just learned about a wonderful thing called the Command Pattern and how it
can simplify adding undo/redo capabilities to an app, so I immediately set
about implementing it. It's working well. Due to the design of my app,
though, I'm having an issue with keyboard shortcuts. Here are the details:
My app manages a data file. This file contains one or more "blocks," and
each block contains one or more "instructions." The UI is set up with a list
view to manage the blocks, a grid to manage the instructions, and a set of
controls (text boxes, sliders, and a couple of check boxes) to actually
manipulate the data in the active row of the grid. (I'm not allowing in-cell
editing on the grid.)
I want to provide the standard Ctrl+Z/Y shortcuts for undo/redo (plus the
CUA Alt+Backspace, since we should all still be supporting the CUA keys,
right folks?), but here's where the problem arises: I don't include edits
you make in the text boxes in the list of things that can be undone. The
text boxes are just a "scratch area" and nothing is actually changed until
you hit the Apply button. Then your entire edit is recorded as an undoable
action. However, I want users to be able to use the built-in undo
functionality that the text box exposes (because experienced users will
probably expect that) so that, for example, if you accidentally highlight a
number and type over it, then realize you wanted the original number (but
don't know what it was since it was a big ugly decimal) then you can hit
Ctrl+Z and rescue yourself without having to hit the Reset button and lose
ALL your changes to that row.
Okay, with those as the requirements, the trick is to capture Ctrl+Z and NOT
activate the menu item Undo (which would undo the last "real" action) and
instead let Ctrl+Z be handled by the text box...IF the text box has anything
to undo. If it doesn't, then assume the user meant to really undo the last
action and do so (cancelling the edit in the process). It's sort of like
using MS Access and editing a row. If you've made changes to a cell, hiting
Escape once undoes that cell, and hitting it a second time (or if the cell
hasn't been modified) cancels changes to the row.
A simple method would be to simply not set Ctrl+Z/Y as menu shortcuts and
handle them purely through code, but I think it's pretty cheesy not to have
those shortcuts display on the Edit menu, especially since it's showing
Ctrl+X/C/V etc. So I guess ultimately my problem is aesthetics! (In VB6 I
would have set the caption of the menu to be <caption><tab><shortcut> and
the shortcut text would be DISPLAYED on the right-hand side of the menu as
if it were a real shortcut. I haven't tried it in WinForms but I don't think
it's the ideal solution in the first place.)
I'm using the 2.0 MenuStrip and not the older Menu controls, if that helps
you give me suggestions. What I think I need to do is find a way to handle
command keys before the MenuStrip does, and I'm currently reading about the
ProcessCmdKey event, but I figured I'd post here in case anyone has done
something similar and has advice. Hopefully the answer will involve
"hooking" and not loads of derived classes....