Wednesday, December 27, 2006

Office 2007 Ribbon: A step backwards in keyboard usability?

I've had a few more chances to play with Office 2007 since installing it from MSDN a couple of weeks ago. Although I'm still getting used to it, I like the mouse UI that the new Ribbon interface presents. What I'm having a lot more trouble working with is the keyboard interface.

I'll confine my comments here to Word 2007, which aside from Outlook (which doesn't use the Ribbon in its main window) is the Office 2007 app I've had the chance to use most so far. I'm a big fan of using keyboard shortcuts to execute frequently-used commands in applications, so in my work with Word 2007 so far, I've tried to continue and use the keyboard shortcuts that I'm familiar with from Word 2003. However, although Office 2007 is advertised to support Office 2003 keyboard accelerator sequences, I have run across a couple of instances already where the Word 2003 keyboard accelerators are altered or broken in Word 2007.

Office 2007 also doesn't provide any "forward breadcrumbing" for Office 2003 keyboard accelerator sequences. For example, if I want to do the Office 2003 command "Table | Insert | Row Above", and I know that the 2003 accelerator sequence starts with Alt+a (Table), I (Insert), but then I'm not sure which letter of "Row Above" is the final step of the sequence ("r"? "a"? something else?) then I'm out of luck in Office 2007; Office 2007 shows the keystrokes of the sequence I've typed so far, but provides no feedback regarding the available keystrokes to continue the sequence.

Using a Word 2003 keyboard accelerator sequence in Word 2007

(In Office 2003, the menu would have opened along with my typing so that I'd be able to glance up and see the underlined letter in "Row Above" which is the correct final keystroke for my command. It turns out to be "a" in this case, in case you were wondering.)

So today, I tried to start using (and memorizing) the new keyboard accelerators built into Office 2007. 2007 does provide nice "forward breadcrumbing" for its new keyboard accelerators; when the Alt key is first pressed, the available shortcuts corresponding to each visible Ribbon option are displayed, and new available options continue to be displayed as you progress through the keystroke sequence.

My problem with the new 2007 accelerator sequences is that in general, they don't seem to lend themselves to memorization very well at all. In Office 2007, the accelerator sequence for "Insert Table Row Above" is alt+j, l, a. (Alt+j, l brings up the Layout ribbon section; a does the Insert Above.) There doesn't seem to be an "obvious" mnemonic for this sequence. (I found myself thinking "Justice League America!" for this particular sequence, but I'd hate to try and extend that paradigm to all 2007 accelerator sequences...)

Most 2003 sequences corresponded fairly well to the command you were executing, making them easy to remember. (Alt+e, f for Edit | Find, for example.) Even the 2003 sequence for Table | Insert | Row Above (alt+a, i, a) was pretty good when you remember that alt+a was the accelerator for the Table menu (alt+t being used for the Tools menu).

The "Insert Table Row Above" is actually not too bad as 2007 accelerator sequences go, either. The 2007 accelerator sequence for the basic "Find" operation is Alt+h, z, n, f, d, f -- 6 keystrokes! And not terribly intuitive, the most questionable sequence being "z, n" to open the "Editing" item on the "Home" ribbon.

The Word 2007 Ribbon after pressing Alt+h. "z, n" is the accelerator key sequence for the "Editing" item.

(You can, of course, alternatively use the Ctrl+F keyboard shortcut to bring up the Find dialog, but many commands don't have a simple Ctrl-key shortcut that can be used for quick access.)

Another point about Office 2007 keyboard accelerator sequences is that they will switch the active (visible) section of the Ribbon; so if you were working with the Home section of the Ribbon, and then type the 2007 "Insert Table Row Above" accelerator sequence, the Ribbon switches to make the Layout section visible, and you need to subsequently manually switch the ribbon back to the Home section (either by clicking it with the mouse or with the accelerator sequence alt+h, Esc, Esc) to make the Home icons visible once again.

My (early) overall impression is that the Office 2007 Ribbon is a nice step forward in UI design for novice/intermediate users (who will normally be activating commands by using the mouse, as opposed to the keyboard) and for the activation of seldom-used commands, but it is in some ways a step backwards for "power users" who prefer to activate frequently-used commands via the keyboard.

Monday, December 18, 2006

New Variable-Width Blog Template

I just updated this blog to use a new template. The main reason for the update was to get the content column of the blog (this column) to be variable-width. Thus, if you increase the size of your browser window, the text in this column should rearrange itself to take advantage of all of the available width.

Previously, the content column was fixed-width. In addition to not taking advantage of the full available width of large browser windows, the previous template also limited me to posting no wide non-wrapping items (in particular, images and code snippets); if such items were too wide, the template would break (and break rather spectacularly on IE!).

It took me a while to come up with HTML/CSS code that would work for what I wanted to do, given that IE (at least through version 6) does not support the min-width CSS style property. Eventually I came across this excellent page by Stu Nicholls which demonstrates a method of getting IE to emulate the min-width style property. (More details on the technique used available here.)

I took advantage of Stu's technique to put together this template, which (seems to!) behave properly in both IE 6 and Firefox 2: The right column is always a constant 270px, and the left column resizes dynamically to take advantage of the remaining available width of the browser window, but never shrinks below 400px or the width of the largest non-wrapping item in the column (whichever is greater). Thanks Stu!

Friday, December 08, 2006

Returning values from C# event handlers

C# events are typically used when a class instance that needs to notify other class instances that reference it that something has taken place.

In some cases, it is useful for an event handler to respond to the event by sending data back to the instance that fired the event. For example, in response to an event fired to give notification that a particular operation is starting, an event handler for the event could respond by returning a boolean value to indicate that the operation should either proceed normally or be aborted.

There are a couple of ways in which returning data in response to handling an event in this manner can be set up.

One is for the event handler method to set a parameter on one of the event method arguments. The System.ComponentModel.CancelEventArgs class is a good example of this. The CancelEventArgs class has a public Boolean property, Cancel, that the event handler method can set to True to indicate that the operation associated with the fired event should be aborted.

A second way for an event handler method to return a value back to the sender is for the delegate that the event is based on to include just that -- a return value. Instead of being declared to return void, the delegate on which the event is based can be defined to return a type such as bool or int.

What happens, though, if there is more than one handler for the event, both of which set the return value (either in a property of one of the event method arguments, or in the return value itself)?

The answer (at least in a single-threaded scenario) is “last-one-in-wins” -- that is, the last event handler method to run is the one whose return value will be received by the code that fired the event. (Event handlers run in the order in which they were registered. A good example of this which uses just plain delegates (not events) can be found in section 15.3 of the C# Language Specification.)

For example, consider this code, which wires up 3 separate event handlers to the event fired by the EventSource class, each of which set the value of the Data parameter of the event’s argument:

class Program
{
  public static void Main(string[] args)
  {
    EventSource eventSource = new EventSource(); 
    Handler handler1 = new Handler(1, eventSource);
    Handler handler2 = new Handler(2, eventSource);
    Handler handler3 = new Handler(3, eventSource);

    eventSource.FireEvent();
  }
}

/// 
/// The IntEventArgs class extends the basic EventArgs class 
/// to add a public parameter of type int named Data.
/// 
class IntEventArgs : EventArgs
{
  public int Data = -1;
}

/// 
/// The EventSource class fires an event that passes an 
/// IntEventArgs as a parameter.
/// 
class EventSource
{
  public delegate void ReturnIntHandler
    (object sender, IntEventArgs eventArgs);
  public event ReturnIntHandler ReturnIntEvent;

  public void FireEvent()
  {
    Console.Out.WriteLine("[EventSource] Firing event...");

    IntEventArgs eventArgs = new IntEventArgs();
    ReturnIntHandler e = this.ReturnIntEvent;
    if (e != null)
    {
      e(this, eventArgs);
    }

    Console.Out.WriteLine("[EventSource] Finished firing event. "
      + "eventArgs.Data: " + eventArgs.Data);
  }
}

/// 
/// The Handler class gets assigned an ID value when it is created 
/// (to distinguish it from other instances of the Handler class), 
/// and handles events of type ReturnIntEvent.
/// 
class Handler
{
  private int _id;

  public Handler(int id, EventSource eventSource)
  {
    this._id = id;
    eventSource.ReturnIntEvent +=
      new EventSource.ReturnIntHandler(eventSource_ReturnIntEvent);
  }

  private void eventSource_ReturnIntEvent
    (object sender, IntEventArgs eventArgs)
  {
    Console.Out.WriteLine("[Handler (" + this._id + ")] "
      + "Handling event, setting Data to " + this._id + ".");
    eventArgs.Data = this._id; 
  }
}

This code produces the following output. Note that the final value of the eventArgs.Data parameter (3) matches the value set by the last event handler method to handle the event.

[EventSource] Firing event...
[Handler (1)] Handling event, setting data to 1.
[Handler (2)] Handling event, setting data to 2.
[Handler (3)] Handling event, setting data to 3.
[EventSource] Finished firing event. eventArgs.Data: 3