The Design of Software (CLOSED)

A public forum for discussing the design of software, from the user interface to the code architecture. Now closed.

The "Design of Software" discussion group has been merged with the main Joel on Software discussion group.

The archives will remain online indefinitely.

Aggregating Commands

How do you aggregate commands?  For a Desktop app, do you use as a switchboard? delegates?  Or not so much -- control events?
Brad Siemens Send private email
Thursday, July 31, 2008
 
 
Yes.
Odysseus Send private email
Friday, August 01, 2008
 
 
I don't fully understand your question, but perhaps you could find useful to look at something like the Command design pattern:

http://en.wikipedia.org/wiki/Command_pattern
not a guru Send private email
Friday, August 01, 2008
 
 
Brad

Some context is always helpful when making such question. The more vague the question, the more vague the answers.
Pablo Send private email
Friday, August 01, 2008
 
 
Boolean operators.
BrotherBeal Send private email
Friday, August 01, 2008
 
 
> How do you aggregate commands?

What do yo mean by "aggregate"?

http://www.codeproject.com/KB/menus/UISwitchboard.aspx suggests you might mean having one class which defines all command actions.

"Aggregate" might also mean creating complicated commands as some ('aggregated') series of simpler commands.
Christopher Wells Send private email
Friday, August 01, 2008
 
 
My bad, long stint, sounded perfectly reasonable in my head. <g>

By aggregate, I meant both.

I guess a better question would have been:  How and where do you handle UI control events?  Do you use the individual events, delegates, switchboard or something else.
Brad Siemens Send private email
Friday, August 01, 2008
 
 
I have a 'Commands' class, which defines a flyweight Command class.
There's an 'internal static' (this is C#) Command instance for each command (e.g. for the "Undo" command), in the Commands class.
Each Command instance is associated with a Toolbar and/or Menu item (or is invoked from a Mock UI for unit-testing).
The application associates a delegate with each Command instance, and can enable/disable specific commands at run-time.
Christopher Wells Send private email
Friday, August 01, 2008
 
 
Excellent, thanks Chris!
Brad Siemens Send private email
Friday, August 01, 2008
 
 
Christopher, how are you associating the control with the command instance?

I use the unique control names strip the prefix and delegate to a command class through a single sub, populating a Command_EventArg along the way (Command\Undo stack).

Is their any pitfalls to this that you're aware of?
Brad Siemens Send private email
Friday, August 01, 2008
 
 
Or is 'there', for the hopeless semantics among us!
Brad Siemens Send private email
Friday, August 01, 2008
 
 
> how are you associating the control with the command instance?

I'm not doing that especially well: e.g. I don't currently support more than one control per command; but because that's in the implementation of one class, I can improve it without affecting the class' public interface or the rest of the application.

I don't like the System.EventHandler class by the way (because the Object and EventArgs parameters aren't useful to me), so I get rid of it by using code like this:

  internal delegate void Clicked();

  //invoked by the application to associate the specified Clicked delegate (implemented by the application) with the specified Command
  internal void onButtonClick(Command command, Clicked clicked)
  {
    Button button = (Button)this[command];
    if (button != null)
    {
      button.Click += delegate(Object sender, EventArgs e) { clicked(); };
    }
  }

Here:
- 'Clicked' defines the signature of the delegate which the application must implement
- 'Button' is a subclass of ToolStripButton
- The indexer is finding the button associated with the specified Command
- The anonymous delegate is associating the application's Clicked delegate with the Button's Click event handler.
Christopher Wells Send private email
Friday, August 01, 2008
 
 
>I don't like the System.EventHandler class by the way...

I would have to agree.

That's interesting Christopher.  How do you handle state for the undo?  Within the command class?
Brad Siemens Send private email
Friday, August 01, 2008
 
 
> How do you handle state for the undo? Within the command class?

No, the Command instances are flyweights; they're static, and contain only little bits of information (e.g. the type of toolstrip subcontrol, text and tooltips for that subcontrol, the associated/contructed toolstrip subcontrol instance[s], and the application delegate to be invoked).

Undo is implemented in the application. A lot of the application delegates are methods of an Editor class. When these methods are invoked, they:

* Create an instance of a command-specific class that implements my IComplexAction interface
* Run that IComplexAction instance
* Put that IComplexAction instance on a stack (of executed actions)

Running an IComplexAction instance does two things:

* Changes the DOM
* Returns an "ISimpleAction undo" instance which, if run, will undo whatever that IComplexAction did

The undo instance gets stored on a stack as well, or something like that.

IComplexAction derives from ISimpleAction ... an ISimpleAction undo can be run in the same way as the original IComplexAction, except that the undo doesn't return an undo-undo (i.e. redo) instance ... I already have the redo instance, which is the original IComplexAction instance which created the undo instance.

In fact, invoking an Editor method (via a Command delegate) might result in several IComplexAction instances being created (e.g. "drag" might be implemented as "cut" followed by "paste") ... these instances would all executed in a bunch, like a single transaction (which is the other type of "command aggregation").
Christopher Wells Send private email
Friday, August 01, 2008
 
 
That's exactly what I was after.

I'm sure I'm missing something but could not the IComplexAction and ISimpleAction be combined into an IAction that simply has 1 to N commands that are executed?

BTW, thanks for taking the time!
Brad Siemens Send private email
Friday, August 01, 2008
 
 
> I'm sure I'm missing something but could not ...

No you're not missing anything: that's what I do. The pseudo code is like:

interface SimpleAction
{
  void invoke(Context context);
}

interface ComplexAction : SimpleAction
{
  //does the same thing to the DOM as base.invoke does, but also creates an instance of a SimpleAction subclass which will undo whatever this ComplexAction did, and pushes this instance onto the undoStack
  void invoke(Context context, Stack<SimpleAction> undoStack);
}

class Actions
{
  Stack<SimpleAction> m_undo = new Stack<SimpleAction>();
  List<SimpleAction> m_redo = new List<SimpleAction>();

  //undo this bunch of actions
  internal void undo(Context context)
  {
    foreach (SimpleAction action in m_undo)
    {
      action.invoke(context);
    }
  }

  //redo this bunch of actions
  internal void redo(Context context)
  {
    foreach (SimpleAction action in m_redo)
    {
      action.invoke(context);
    }
  }

  //invoked one or more times when this Actions instance is constructed
  void add(ComplexAction action, Context context)
  {
    //invoke the action and add to the undo stack
    action.invoke(context, m_undo);
    //and remember this in case we need to redo
    m_redo.Add(action);
  }

  ... various methods called when this Actions instance is constructed, which invoke the add method one or more times ...
}

class Editor
{
  ... various methods, which create Actions instances and add them to the m_canUndo stack, and which are invoked from Command delegates ...

  //stack of Actions which have been done
  Stack<Actions> m_canUndo = new Stack<Actions>();
  //stack of Actions which have been undone
  Stack<Actions> m_canRedo = new Stack<Actions>();

  //method invoked to undo the recent Actions instance
  internal void onUndo(Context context)
  {
    if (m_canUndo.Count == 0)
      return;
    Actions action = m_canUndo.Pop();
    action.undo(context);
    m_canRedo.Push(action);
  }

  internal void onRedo(Context context) { ... similar ... }
}
Christopher Wells Send private email
Friday, August 01, 2008
 
 
You might want to make you command responsible for undoing themselves, instead of putting the 'reverse' action on the undo stack.

Also, redoing by calling invoke (again) is really a special case. You may want to account for that by putting that knowledge in the actions themselves instead of in the command processor.

Combine the above two and you get a Command with execute, undo and redo.

Btw, a solution that uses flyweights and hands them the appropriate context is not the best fit for all situations. You may want to evaluate that for your own app instead of just copying Christopher's approach (it may make perfect sense in his app but not in yours).
Yanic Inghelbrecht Send private email
Saturday, August 02, 2008
 
 
The action class aren't fly-weights.

If the same command is invoked twice, 2 actions will be instantiated.

Each action instance contains distinct and significant member data: for example, an 'undo-delete' action instance contains the deleted document fragment to be reinserted.

The reason for having a context passed as a method parameter is that there is some short-lived data that cannot be stored as member data in the long-lived action instance: the short-lived data in the context includes, especially, the current System.Drawing.Graphics instance.
Christopher Wells Send private email
Saturday, August 02, 2008
 
 
So your Commands are just the glue that binds the Actions to the UI?
Yanic Inghelbrecht Send private email
Saturday, August 02, 2008
 
 
Yes, the Commands class and its static Command instances are glue between methods of the GUI-less Editor class, and the GUI toolstrip and/or menu instances. It might play the role of what Brad called a switchboard.

An Actions instance, which contains one or more ComplexAction and SimpleAction instances, is created and invoked (and remembered, in case it needs to be undone) each time an Editor method is invoked.
Christopher Wells Send private email
Saturday, August 02, 2008
 
 
Ah, now I see.. I got confused because in java, Actions are the glue. Thanks for the clarification.
Yanic Inghelbrecht Send private email
Sunday, August 03, 2008
 
 

This topic is archived. No further replies will be accepted.

Other recent topics Other recent topics
 
Powered by FogBugz