Click here to Skip to main content
15,881,380 members
Articles / Programming Languages / C#
Article

Menu State Handling

Rate me:
Please Sign up or sign in to vote.
4.50/5 (7 votes)
23 Aug 2008CPOL5 min read 38.7K   213   31   5
Handling menu state in WinForms using the Decorator pattern.

Introduction

Handling the state of a menu based on the state of the application is one of the primary requirements in all applications. The more number of states exhibited by the application, the more complex the state handling becomes. I tried to get a good pattern for handling the state management of a menu, that is extensible and neatly layered. Finally, I sat down for my own implementation. This implementation uses the Decorator pattern. Let me take a simple example and explain the implementation.

Example

Consider a simple phone book application. The application allows the user to add, modify, and remove a contact. A contact would have a name, a residence phone number, and a mobile number. There are two types of users who can use the phone book: the Admin and Guest users. The Admin user can perform all operations. The Guest user cannot add or remove a contact, but can update any contact. These are the Use Cases that need to be supported by the application. The options are provided as drop down menus. The menu options should be enabled or disabled based on two aspects.

  1. Based on the user operation. When the user selects an operation, the user has to select the Save option to complete it, or cancel the operation if the user needs to move with another operation. For example, if the user has selected to update a contact, the user has to save after modifying the contact, or has to cancel it if the user wants to move to another operation. While the update operation is active, all other operations except Save and Cancel should be disabled.
  2. Based on the type of user who has logged in. Disable the appropriate menu options not applicable for the type of logged in user.

I would call the above rules as policies. The first one, I would call a State Policy. The reason I chose the name State Policy is any operation on the model results on a state change. In the true sense, it’s the Model state that drives the View. The second rule is called as User Policy. These policies govern the way the menu behaves.

Basic Design

The phone book uses the Model View Presenter (MVP) design pattern for coordinating user interactions and phone book model operations. The MVP triad comprises of the PhoneBookPresenter, PhoneBookView, and the PhoneBookModel. The state of the View is driven by the state of the Model. Whenever the Model’s state changes, the View gets a notification and updates itself (the typical Observer). The View has the menu which would be our point of interest. How do we enable and disable the menu items based on the policies mentioned? In a real world application, there could be a bunch of complex policies, and such policies would keep piling as user requirements change.

The PhoneBookModel has a set of pre-defined states that it can be in, and a set of commands that can be applied on the Model. These are defined by two enumerations: PhoneBookState and PhoneBookCommands.

C#
internal enum PhoneBookState
{
    NewEntry,
    UpdateEntry,
    RemoveEntry,
    View,
    Search,
    Locked
}

[Flags]
internal enum PhoneBookCommands
{
    New     = 0x0001,
    Update  = 0x0002,
    Remove  = 0x0004,
    Save    = 0x0010,
    Search  = 0x0020,
    Cancel  = 0x0080,
    All     = New | Update | Remove | Save | Search | Cancel
}

For a given state, there can only be a set of commands allowed. The PhoneBookView queries the PhoneBookModel for the set of commands whenever the PhoneBookModel state changes and updates its menu.

The PhoneBookModel needs to filter the set of commands based on three factors:

  1. The number of contacts in the PhoneBook.
  2. The Model’s state policy.
  3. The User Policy.

This where the Decorator comes into play. The Decorator allows you to attach additional responsibilities to the object, dynamically. It also gives us the flexibility to extend the functionalities. By now, you should have guessed the implementation. Yes, I have separate Decorator classes which decorate the commands list based on the policy it provides.

C#
internal class StatePolicyCommandDecorator : PhoneBookCommandsDecorator

internal class UserRolePolicyCommandDecorator : PhoneBookCommandsDecorator

Then, all the PhoneBookModel needs to do is stack up the Decorators it needs and call the appropriate method to do the decoration.

C#
commandsDecorator = new StatePolicyCommandDecorator( 
        new UserRolePolicyCommandDecorator(this, this.user.Role));

//Invoke the method which does the required decorations on the model commands.
internal PhoneBookCommands GetModelCommands() 
{
    return this.commandsDecorator.GetCommands();
}

The advantage we get out of this approach is that, we can keep adding policies on the Model commands without making the Model heavy. There’s a good amount of encapsulation and layering.

Implementation

Let's get down to the implementation. I shall be focusing on how the commands get decorated and how the menu updates its state. Apart from this, it's simple MVP that glues things together.

I have a generic interface defined for the Decorator, with two generic type parameters, which has two generic functions. The GetState returns the current state of the Model, and GetCommands returns the decorated commands list.

C#
internal interface IModelCommandsDecorator<TState, TCommands>
{
    TState GetState();
    TCommands GetCommands();
}

The concrete implementation can define any type for the TState and TCommands generic type parameters.

The IModelCommandsDecorator is implemented by the PhoneBookCommandsDecorator with the PhoneBookState and PhoneBookCommands enums.

C#
internal abstract class PhoneBookCommandsDecorator : 
         IModelCommandsDecorator<PhoneBookState, PhoneBookCommands>
{
    private IModelCommandsDecorator<PhoneBookState, PhoneBookCommands> model;
    internal PhoneBookCommandsDecorator( IModelCommandsDecorator<PhoneBookState, 
                                         PhoneBookCommands> model) 
    {
        this.model = model;
    }

    public virtual PhoneBookState GetState()
    {
        return this.model.GetState();
    }

    public virtual PhoneBookCommands GetCommands()
    {
        return model.GetCommands();
    }
}

Let's first look at the PhoneBookModel, then the two decorators. The PhoneBookModel also implements IModelCommandsDecorator. Its implementation of GetCommands is fairly straightforward. It just looks at the contacts count and disables the update, search, and remove commands.

C#
internal class PhoneBook : IModelCommandsDecorator<PhoneBookState, PhoneBookCommands>
{
    public PhoneBookCommands GetCommands()
    {
        PhoneBookCommands disableCommands = PhoneBookCommands.Update | 
                  PhoneBookCommands.Search | PhoneBookCommands.Remove;
        return (this.contacts.Count == 0 || 
               (this.contactBuffer.GetType() == typeof(NullContact)) ) ? 
               (this.commands & ~disableCommands ) : this.commands;
    } 
}

The GetPhoneBookCommands is the function which would be invoked by the View. That internally calls the private member's (commandDecorator) GetCommands method, which does the job of calling the appropriate decorators to do the decoration on the commands.

C#
internal PhoneBookCommands GetPhoneBookCommands () 
{
    return this.commandsDecorator.GetCommands();
}

Let's look at the StatePolicyCommandDecorator next. The constructor takes an IModelCommandsDecorator<PhoneBookState, PhoneBookCommands> instance. The objective of this decorator is to take the commands from the decorator instance that is passed and decorate them depending on the policy. This decorator, internally, has a dictionary that maps each PhoneBookState to the list of PhoneBookCommands. The overridden GetCommands basically gets the existing set of commands from the inherited member (modelDecorator) through the base.GetCommands() method, and “ands” it with the commands list defined in the private state2CommandsMap dictionary member for the current state. This is how the commands get decorated by the State Policy. I have used a simple bit manipulation on the enums. Any custom implementation can be accommodated in the Decorator based on the requirement of your application.

C#
using State2CommandsMap = Dictionary<PhoneBookState, PhoneBookCommands>;

internal class StatePolicyCommandDecorator : PhoneBookCommandsDecorator
{
    private State2CommandsMap state2CommandsMap;
    internal StatePolicyCommandDecorator(IModelCommandsDecorator<PhoneBookState,
                                         PhoneBookCommands> model) : base(model) 
    {
        this.MapCommands2State();
    }

    public override PhoneBookCommands GetCommands()
    {
        return (base.GetCommands() & this.state2CommandsMap[this.GetState()]);
    }

    private void MapCommands2State()
    {
        this.state2CommandsMap = new State2CommandsMap();
        this.state2CommandsMap[PhoneBookState.NewEntry] = 
             PhoneBookCommands.Save | PhoneBookCommands.Cancel;
        this.state2CommandsMap[PhoneBookState.UpdateEntry] = 
             PhoneBookCommands.Save | PhoneBookCommands.Cancel;
        this.state2CommandsMap[PhoneBookState.RemoveEntry] = PhoneBookCommands.Cancel;
        this.state2CommandsMap[PhoneBookState.View] = 
             PhoneBookCommands.All & ~PhoneBookCommands.Save & ~PhoneBookCommands.Cancel;
        this.state2CommandsMap[PhoneBookState.Search] = PhoneBookCommands.Cancel;
        this.state2CommandsMap[PhoneBookState.Locked] = PhoneBookCommands.Cancel;
    }
}

The same logic is applied in the UserRolePolicyCommandDecorator, with a similar map being maintained between the UserRole and PhoneBookCommands. The overridden GetCommands does an “and” of the existing commands and the mapped commands for the logged in user.

C#
using UserRole2CommandsMap = Dictionary<UserRole, PhoneBookCommands>;
this.userRole2CommandsMap[UserRole.Admin] = PhoneBookCommands.All;
this.userRole2CommandsMap[UserRole.Guest] = PhoneBookCommands.Update | 
     PhoneBookCommands.Search |PhoneBookCommands.Save | PhoneBookCommands.Cancel;

public override PhoneBookCommands GetCommands()
{
    return (base.GetCommands() & this.userRole2CommandsMap[this.role]);
}

Now, let's look at the way the PhoneBookView updates the menu based on the state. When the PhoneBook Model changes its state, a state changed event is fired.

C#
internal event EventHandler StateChanged;

The PhoneBookView’s OnStateChangedEventHandler invokes a method to update the menu items.

C#
private void UpdateMenuState() 
{
    PhoneBookCommands commands = this.phoneBookModel.GetPhoneBookCommands();
    ToolStripMenuItem menuItem = (ToolStripMenuItem)this.phoneBookMenu.Items[0];
    foreach (PhoneBookMenuItem item in menuItem.DropDownItems)
    item.Enabled = ((item.Command & commands) != 0);
}

Conclusion

I have provided a fully functional Phone Book application along with this article. It covers the Use Cases discussed. I have basically covered how to handle a menu. The same logic can be extended for other controls.

Happy coding!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralI want something like this Pin
saiful_vonair6-Dec-09 21:38
saiful_vonair6-Dec-09 21:38 
Generalpaplu code Pin
akadiwala18-Jun-07 23:23
akadiwala18-Jun-07 23:23 
GeneralSource Code Pin
zavudrac24-Jan-07 4:18
zavudrac24-Jan-07 4:18 
GeneralRe: Source Code Pin
Krishnan Srinivasan24-Jan-07 17:41
Krishnan Srinivasan24-Jan-07 17:41 
GeneralRe: Source Code Pin
zavudrac25-Jan-07 0:08
zavudrac25-Jan-07 0:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.