[MonoDevelop] Ideas for a new command system

Lluis Sanchez lluis@ximian.com
Sat, 09 Apr 2005 02:22:03 +0200


Hi!

The next target in my quest for improving the architecture of MD is the
command system. There are two reasons why I don't like the current
design:

      * It is not possible to change the behavior of a command depending
        on the context in which it is run. For example, the Delete
        command deletes the selected text in the editor, but we should
        be able to use the same command for deleting the selected file
        in the solution pad (if the pad has the focus), or whatever. Any
        addin should be able to provide a custom behavior for an
        existing command.
      * It is not easy to handle the status of commands. Commands can be
        enabled/disabled using conditions, but conditions must be
        specified in the addin XML file and are very limited.

What I propose is a very different approach, so feedback is greatly
welcome (BTW, the main concept is taken from MS's MFC class library, not
a great library, but I like the approach it uses for command handling).

First of all, there is a global list of commands, which is independent
from where those commands are used. The command list would be
defined/extended in the addin xml like this:

<Extension path = "/SharpDevelop/Commands">
	<Command id = "EditCommands.Copy"
			_label = "Copy"
			icon = "Icons.16x16.CopyIcon" 
			shortcut = "Control|C"/>
	<Command id = "EditCommands.Paste"
			_label = "_Paste" 
			icon = "Icons.16x16.PasteIcon" 
			shortcut = "Control|V"/>
	<Command id = "EditCommands.Delete"
			_label = "_Delete" 
			icon = "Icons.16x16.DeleteIcon" 
			shortcut = "Del"/>
</Extension>

Then, we can define menus and toolbars by refering those commands:

<Extension path =
"/SharpDevelop/Views/ProjectBrowser/ContextMenu/ProjectFileNode">
	<MenuItem id = "Copy" command = "EditCommands.Copy"/>
	<MenuItem id = "Paste" command = "EditCommands.Paste"/>
	<MenuItem id = "Delete" command = "EditCommands.Delete"/>
</Extension>

The big change is how commands are executed. Instead of implementing a
single class for each command, we can implement command handler methods
in any class. For example, to handle the Delete command in the text
editor I would write a handler like this:

[CommandHandler (EditCommands.Delete)]
void OnDeleteCommand ()
{
	// Delete the selection
}

If we want to implement the Delete command in a custom pad, we would add
a similar command handler in that pad. So, we have two handlers for the
same command in two different classes. Which one of them will be
executed when clicking on the Delete menu item? It depends on the
context.

When the command is clicked, the command manager looks for a command
handler by following a command route. This route begins at the widget
that has the focus and goes up in the parent chain. This means that if
we are editing some text in the editor, the delete command handler in
the editor will be executed. If we move the focus to the solution pad,
then the pad will get the commands.

The command manager will also automatically disable or hide commands for
which there isn't a handler in the active command route. This gives a
lot a consistency to the menu and toolbars, since options and buttons
will be grayed out when they can't be used (and there is no need to
write code for this, it comes for free).

If the status of a command depends on some internal logic, we can
implement that logic in some special command update handlers. For
example:

[CommandUpdateHandler (EditCommands.Delete)]
void OnUpdateDeleteCommand (CommandInfo commandInfo)
{
	if (the_selected_tree_node_can_be_deleted()) {
		commandInfo.Enabled = true;
		commandInfo.Text = "Delete " + current_node.Name;
	} else {
		commandInfo.Enabled = false;
	}
}

The command manager will look for a command update handler in the
current command route. If found, it will call the handler and will
update the menu items and buttons linked to that command accordingly. If
not found, it will disable them.

This is more or less the idea. There are many more details, such as
being able to customize the command route, defining global command
handlers for global commands, menu builders for the "Windows" menu list,
etc., but I just described the fundamental idea.

Comments?
Lluis.