The MruMenuManager Component


Applications focused on document management often employ a special UI feature to provide quick access to documents currently receiving high rotation. This set of documents comprises the most recently used (MRU) and are typically made available as a menu item located in the File menu.

An MRU menu typically exhibits the following characteristics:

  • It provides access to the last n most recently used documents, where n is a predefined number that's provided a default at application installation. Users can change this number.

  • Each document is represented as a single menu strip item that contains the document's file name as the text value and a numeric menu access key that represents the file's position in the list of most recently used from most recent to least recentthat is, 1 to n.

  • When an MRU menu strip item is clicked, its corresponding file should be opened.

  • When a file is opened or saved, a menu strip item for it should be added to the MRU menu in the most recent position, with the remaining menu strip items repositioned accordingly.

  • When an MRU menu strip item is clicked, it should be moved to the most recent position, with the remaining menu strip items repositioned accordingly.

  • If the file name is too long to display within the available width of a menu, it should be shortened by replacing the removed text with an ellipsis. The removed text usually contains one or more folder names in sequence.

  • The total number of MRU menu strip items displayed is the lesser of the number of files loaded and n.

  • If a user changes n and n is now less than the total number of MRU menu strip items currently displayed, all superfluous MRU menu strip items should be removed from the MRU menu.

  • The MRU menu should work equally from SDI and MDI applications.

  • If the MRU menu doesn't contain any items, it should be disabled.

  • Users should have the option to remove an item from the MRU menu if the file doesn't exist.

  • The set of items in an MRU menu can be displayed either within an existing menu or within a submenu. Microsoft Word favors the former, and VS05 is partial to the latter.

  • An MRU menu should remember the most recently used files from one application session to the next.

That's a lot of work for such a small feature. If the idea of building this functionality yourself doesn't get you up for the big game, you can use the reusable MRU component, MruMenuManager, which you'll find with the samples for this chapter. This component addresses each of our criteria for MRU menus.

Getting an MRU Menu

The MruMenuManager component can be dropped onto a form like any other component. After it's there, MruMenuManager first needs a reference to a menu item, which it will use as a placeholder to add and remove MRU menu items on your behalf. The MRU menu typically resides beneath a top-level menu item, so you need to create a placeholder menu item specially, as shown in Figure F.14.

Figure F.14. Creating a Placeholder Menu Item for the MRU


After you have the placeholder menu item, you simply reference it from the MruMenuManager component's MruListMenu property, shown in Figure F.15.

Figure F.15. Pointing the MruMenuManager at the MRU Placeholder Menu Item


With that in place, MruMenuManager has the basic piece of information needed to provide its services. The first of those is configuring how your MRU menu will look.

Configuring the MRU Menu Appearance

MruMenuManager offers three main configurations: menu display style, the maximum number of items displayed, and the maximum text width.

Classically, there are two types of display styles for MRU menus: in menu and in submenu. In-menu MRU menus display zero or more most recently used menu items within the submenu of a top-level menu item. In-submenu MRU menus, on the other hand, display their menu items from a cascading submenu of a top-level menu item. Figure F.16 illustrates an in-menu MRU menu, and Figure F.17 shows an in-submenu MRU menu.

Figure F.16. In-Menu MRU Style


Figure F.17. In-Submenu MRU Style


MruMenuManager allows you to choose either style via its DisplayStyle property, which can be one of the two MruListDisplayStyle enumeration values:

enum MruListDisplayStyle {    InMenu,    InSubMenu }


This property is most easily set from the Properties window, as shown in Figure F.18.

Figure F.18. Specifying the MRU Menu's Display Style


When you choose the InMenu display style, the menu item that you specified as the MruListMenu is replaced with the actual MRU menu items. If you choose InSubMenu, the MruListMenu remains visible with the text you specified for it, although it is disabled when there are no MRU menu items.

The next option you can specify is the maximum number of items the MRU menu will contain. MruMenuManager has a default value of 10 items, but most applications typically allow users to specify their own number from the Tools | Options menu. This value is captured by MruMenuManager's MaximumItems property.

The final option is to specify the maximum display width of your MRU menu items. The norm for an MRU menu is to display an entire file path, unless the file path is too long, like the one in Figure F.19.

Figure F.19. A Very Wide MRU Menu Item


You use the TextWidth property to limit the number of characters to display. If a file path's length exceeds the value stored in TextWidth, MruMenuManager truncates the file path by replacing one or more folder elements with an ellipsis, as shown in Figure F.20.

Figure F.20. Truncating Very Wide MRU Menu Items


With the appearance taken care of, we start adding menu items to MruMenuManager so that it can display them.

Adding a File to the MRU Menu

To add files to MruMenuManager, you invoke the Add method, which accepts a single file path string argument. You need to write the code to call this method, typically from wherever you open or save documents. For example, here's where they would go in the SDI Rates of Return application:

// RatesOfReturnForm.cs partial class RatesOfReturnForm : Form {    ...    void fileDocument_ReadDocument(      object sender, SerializeDocumentEventArgs e) {      ...      // Add to MRU menu      this.mruMenuManager.Add(e.Filename);    }    void fileDocument_WriteDocument(      object sender, SerializeDocumentEventArgs e) {      ...      // Add to MRU menu      this.mruMenuManager.Add(e.Filename);      // Let file document take care of saving      e.Handled = false;    }    ... }


As you can see, MruMenuManager.Add works very nicely from handlers for the FileDocument component's ReadDocument and WriteDocument events.

As it turns out, that's the minimum work you need to do to get an MRU menu to display MRU menu items. MruMenuManager ensures that only a maximum number of characters are displayed; that the last menu item you added is moved to the top of the list; and that each item has a number access key, from 1 to the maximum number of allowable items. But displaying menu items is only half the job; the other half is to handle user selection of those items to open the corresponding files.

Opening an MRU File

For users to open a file from the MRU menu, MruMenuManager needs a file path, and it needs to know when an MRU menu item is clicked so that it can notify the client application, which implements the deserialization logic to open a file. To manage this, MruMenuManager uses a custom tool strip menu item both to store the file path and to hook its Click event. When the custom tool strip menu item is clicked, it extracts the associated file path and fires the MruMenuItemClick event, to which it passes the file path via an MruMenuItemClickEventArgs. Your application should handle MruMenuItemClick by calling the appropriate file open method:

// RatesOfReturnForm.cs partial class RatesOfReturnForm : Form {    ...    void mruMenuManager_MruMenuItemClick(      object sender, MruMenuItemClickEventArgs e) {      this.fileDocument.Open(e.Filename);    }    ... }


In some cases, the file pointed to by an MRU menu item may have been deleted or moved, thereby breaking the MRU menu item. In these cases, MruMenuManager fires the MruMenuItemFileMissing event, passing an MruMenuItemFileMissingEventArgs object. This object includes the path to the missing file and an option to have MruMenuManager remove the file from the MRU menu or keep it there. Consequently, you can allow the user to decide what to do when a file goes walkabout:

// RatesOfReturnForm.cs partial class RatesOfReturnForm : Form {    ...    void mruMenuManager_MruMenuItemFileMissing(      object sender, MruMenuItemFileMissingEventArgs e) {      DialogResult res =        MessageBox.Show(          "Remove " + e.Filename + "?",          "Remove?",          MessageBoxButtons.YesNo);      if( res == DialogResult.Yes ) {        e.RemoveFromMru = true;      }    }    ... }


When you set RemoveFromMru to true, MruMenuManager removes the related item from the MRU menu, never to be seen again.

Persisting the MRU Menu across Application Sessions

One thing you do want to be seen again is the MRU menu, with menu items, the next time users open your application. Because the files that make up the MRU Menu are dictated by the current user of the current application, it makes sense to leverage Windows Forms settings support to store MRU files on a per-user basis.[10] MruMenuManager does this via two properties: UseSettings and SettingsKey.

[10] See Chapter 15: Settings for the lowdown on application and user settings.

UseSettings is a Boolean that allows you to choose whether your MRU menu items are automatically stored as user settings; by default, it is true. SettingsKey is the string key value that MruMenuManager uses to distinguish its settings in the user.config file. By default, the SettingsKey value is set to MruMenuManager.MruItems, although you can change it as necessary. So, by default, your items are persisted to user.config:

<?xml version="1.0" encoding="utf-8"?> <configuration>    ...    <userSettings>      <MruControlLibrary.MruMenuManagerSettings.MruMenuManager.MruItems>        <setting name="MruListItems" serializeAs="Xml">          <value>            <ArrayOfString xmlns:xsi="http://www.w3.org/..."                           xmlns:xsd="http://www.w3.org/...">               <string> C:\...\Desktop\S&P.ror</string>               <string> C:\...\OakmarkFund.ror</string>            </ArrayOfString>          </value>        </setting>      </MruControlLibrary.MruMenuManagerSettings.MruMenuManager.MruItems>    </userSettings> </configuration>


If you prefer to control when settings are loaded and saved, you can eschew the default behavior by setting UseSettings to false and calling MruMenuManager.LoadSettings and MruMenuManager.SaveSettings directly. Alternatively, you can retrieve the list of file paths stored via MruMenuManager.FileNames and persist them somewhere beyond the domain of MruMenuManager. In most cases, the default, automatic option should do the trick.

MruMenuManager and MDI Applications

MruMenuManager requires a little more effort when used by MDI applications. Even though the MDI parent form handles File | Open, it's the FileDocument component on the MDI child form that does the work of reading the actual document. Furthermore, the MDI child form handles File | Save, using its FileDocument to write the document. The MDI parent form needs to detect when both reading and writing occurs to update its MRU menu appropriately. Consequently, the MDI parent form must handle the ReadDocument and WriteDocument events that are raised by the MDI child form's FileDocument Component. First, this means exposing the FileDocument component from the MDI child form:

// RatesOfReturnForm.cs partial class RatesOfReturnForm : Form {    ...    public FileDocument FileDocument {      get { return this.fileDocument; }    }    ... }


This allows the MDI parent form to hook the appropriate events:

// MdiParentForm.cs partial class MdiParentForm : Form {    ...    void newToolStripMenuItem_Click(object sender, EventArgs e) {      // Create and show a new child      RatesOfReturnForm child = new RatesOfReturnForm();      HookMDIChildFileDocument(child);      child.MdiParent = this;      child.Show();    }    ...    void HookMDIChildFileDocument(RatesOfReturnForm mdiChild) {      mdiChild.FileDocument.ReadDocument +=        MDIChildFileDocument_ReadDocument;      mdiChild.FileDocument.WriteDocument +=        MDIChildFileDocument_WriteDocument;      mdiChild.FormClosing +=        MDIChild_FormClosing;    }    void UnhookMDIChildFileDocument(RatesOfReturnForm mdiChild) {      mdiChild.FileDocument.ReadDocument -=        MDIChildFileDocument_ReadDocument;      mdiChild.FileDocument.WriteDocument -=        MDIChildFileDocument_WriteDocument;      mdiChild.FormClosing -=        MDIChild_FormClosing;    }    ...    void MDIChildFileDocument_ReadDocument(      object sender, SerializeDocumentEventArgs e) {      // Add to MRU menu      this.mruMenuManager.Add(e.Filename);    }    void MDIChildFileDocument_WriteDocument(      object sender, SerializeDocumentEventArgs e) {      // Add to MRU menu      this.mruMenuManager.Add(e.Filename);      // Let file document take care of saving      e.Handled = false;    }    void MDIChild_FormClosing(object sender, FormClosingEventArgs e) {      UnhookMDIChildFileDocument((RatesOfReturnForm)sender);    } }


As you can see, even though you have to spread the code over two forms, it isn't much different with the MDI child Rates of Return form from what it is with the SDI Rates of Return form.




Windows Forms 2.0 Programming
Windows Forms 2.0 Programming (Microsoft .NET Development Series)
ISBN: 0321267966
EAN: 2147483647
Year: 2006
Pages: 216

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net