Approach #2: Custom Classes and Dynamic Controls
In this approach, custom classes, events and dynamic creation of controls will be used to give more robust flexibility to each form while maintaining a consistent feel for the user. This approach uses the concept that a form can only be in one "mode" ( Edit, Add, Delete, etc...) at a time. For this concept to work we need to create a class to hold information about the mode. This information will determine how the mode is entered (button, keyboard command, both, etc...) and what other modes are visible while the form is in a particular mode. As the basis for this approach the form mode class will have to be created first.
Right-click on the project in the Solution Explorer, select Add, then Class. Name the class "FormMode.cs
" and click Add. Create a public enumeration that will allow consistent naming and use of form modes across all forms. Call the enumeration "Mode" and populate it with 'Default', 'Add', 'Edit', 'Delete', and 'Exit'. The FormMode
properties and constructor need to be added next. See the below code extract to get the full setup of what the FormMode
class should look like when fully created.
public class FormMode
{
public enum Mode
{
Default = 1,
Add = 10,
Edit = 20,
Delete = 30,
Exit = 40
}
public Mode SelectedMode { get; set; }
public bool ShowButton { get; set; }
public Keys ModeHotKey { get; set; }
public string ButtonText { get; set; }
public List ModesToHide { get; set; }
public bool IsModeEnabled { get; set; }
public FormMode(Mode selectedMode, bool showButton, Keys modeHotKey, string buttonText,List modesToHide, bool isModeEnabled)
{
SelectedMode = selectedMode;
ShowButton = showButton;
ModeHotKey = modeHotKey;
ButtonText = buttonText;
ModesToHide = modesToHide;
IsModeEnabled = isModeEnabled;
}
}
Now that the FormMode
class has been created, the base form, or the form that all other forms will inherit from, can be created. Reusing the BaseForm.cs
form the first approach remove the added code to the override of the OnKeyDown
method, but leave the override as it will still be used. In the designer add a FlowLayoutPanel
control and dock it to the right side the form. Name the control "flpButtons". This is all that needs to be added to the base form from the designer so switch into code view.
The base form will need a consistent way to communicate that the form's mode is changing and to do that a custom event will be used. In the code view of BaseForm.cs
add the following code to create the event that will be fired each time the form's mode changes.
public delegate void FormModeChangedEventHandler(FormModeChangedEventArgs e);
public event FormModeChangedEventHandler FormModeChanged;
public class FormModeChangedEventArgs : EventArgs
{
public FormModeChangedEventArgs(FormMode.Mode oldFormMode, FormMode.Mode
newFormMode)
{
OldFormMode = oldFormMode;
NewFormMode = newFormMode;
}
public FormMode.Mode OldFormMode { get; set; }
public FormMode.Mode NewFormMode { get; set; }
}
Next create one property and two collections on the base form. The property will hold the current form mode and the first collection will contain all available modes the form can possibly have, while the second collection will contain a more dynamic list of the modes that are currently available to the user.
private FormMode.Mode currentFormMode = FormMode.Mode.Default;
public FormMode.Mode CurrentFormMode
{
get
{
return currentFormMode;
}
set
{
FormMode.Mode oldFormMode = currentFormMode;
currentFormMode = value;
this.FormModeChanged(new FormModeChangedEventArgs(oldFormMode,
currentFormMode));
InitializeButtons();
}
}
protected List formModeCollection = new List();
public List FormModeCollection
{
get
{
return formModeCollection;
}
set
{
formModeCollection = value;
}
}
private List formModeActiveCollection = new List();
The base form now has all the core elements to drive mode-based behavior consistently on all inherited forms within the application. What is missing is the helper method to control the buttons on the form based on the current mode, the OnKeyDown
method override and button click events. The helper method InitializeButtons
will be used to determine what modes are available based on the current mode, then determine if those available modes have a button to display. The below code snippet is what the InitializeButtons
method should look like when finished.
protected void InitializeButtons()
{
formModeActiveCollection = new List();
// Find the current form mode object to determine
// what other modes should not be displayed
FormMode fmCurrent = null;
foreach (FormMode curMode in FormModeCollection)
{
if (curMode.SelectedMode == CurrentFormMode)
{
fmCurrent = curMode;
break;
}
}
if (fmCurrent == null)
throw new Exception("Current FormMode not found!");
foreach (FormMode formmode in FormModeCollection)
{
bool matchFound = false;
foreach (FormMode.Mode mode in fmCurrent.ModesToHide)
{
if (formmode.SelectedMode == mode)
{
matchFound = true;
break;
}
}
// If a match was found the mode of that form mode
// shouldn't be enabled for action, either through
// button interface or keyboard
if (matchFound)
{
formmode.IsModeEnabled = false;
continue;
}
else
{
formmode.IsModeEnabled = true;
formModeActiveCollection.Add(formmode);
}
}
this.flpButtons.Controls.Clear();
// Using the collection of modes that are active build
// the list of buttons to display in the Flow Layout Panel
foreach (FormMode fmButton in formModeActiveCollection)
{
Button buttonShell = new Button();
buttonShell.Size = new System.Drawing.Size(150, 23);
buttonShell.TabStop = false;
buttonShell.Text = fmButton.ButtonText;
buttonShell.Tag = fmButton;
// The ShowButton allows a form mode to still be
// enabled, but not show a button
buttonShell.Visible = fmButton.ShowButton;
buttonShell.Click += new EventHandler(buttonShell_Click);
this.flpButtons.Controls.Add(buttonShell);
}
}
The click event for the dynamically created buttons should change the form's mode to the mode of the button. The below code uses the FormMode
object placed in the Tab property of the button to change the from's mode to that of the button's mode, via the FormMode
object. When finished the buttonShell_Click
event handler should have the following code.
private void buttonShell_Click(object sender, EventArgs e)
{
if (((sender as Button).Tag is FormMode))
{
FormMode formMode = ((sender as Button).Tag as FormMode);
CurrentFormMode = formMode.SelectedMode;
}
}
The last thing to code on the base form is the override method for the OnKeyDown method.
protected override void OnKeyDown(KeyEventArgs e)
{
// Handles form action key codes
foreach (FormMode formmode in formModeActiveCollection)
{
if (formmode.ModeHotKey == e.KeyCode && formmode.IsModeEnabled)
{
CurrentFormMode = formmode.SelectedMode;
e.Handled = true;
}
}
base.OnKeyDown(e);
}
The base form is now complete and ready to be used by the application's forms through inheritance. Open the designer for MainForm.cs
in Microsoft Visual Studio and remove the two existing buttons along with their handler methods. Since the buttons on the form will be dynamically driven there is no need to keep the static buttons. With the form selected open up the events filter of the Properties window and find the new FormModeChanged
event. Double-click the event to create the handler method for it in the code.
In the code view, MainForm is already inheriting from BaseForm from the first approach. If it were not already inheriting from BaseForm the FormModeChanged event would not have been available in the designer's Properties windows. Since MainForm inherits from BaseForm all the behind the scenes wiring is already in place to handle form modes and fire the FormModeChanged
event each time the mode changes. What needs to be done now, and on each form of the application is tell the form what modes are available. The below code block from MainForm's constructor shows MainForm being configured to allow the 'Default', 'Add', 'Edit' and 'Exit' modes.
// Default Mode
FormModeCollection.Add(new FormMode(FormMode.Mode.Default, false, Keys.KeyCode, string.Empty, new List(), false));
// Add Mode
List hideListAdd = new List();
hideListAdd.Add(FormMode.Mode.Edit);
FormModeCollection.Add(new FormMode(FormMode.Mode.Add, true, Keys.F1, "F1 - Add", hideListAdd, true));
// Edit Mode
List hideListEdit = new List();
hideListEdit.Add(FormMode.Mode.Add);
FormModeCollection.Add(new FormMode(FormMode.Mode.Edit, true, Keys.F2, "F2 - Edit", hideListEdit, true));
// Exit Mode
FormModeCollection.Add(new FormMode(FormMode.Mode.Exit, true, Keys.Escape, "Esc - Exit", new List(), true));
If the application is run at this point nothing shows on the form because the InitializeButtons
method is not being called. Since the InitializeButtons
method cannot be invoked until there are FormMode objects in the collection the call to the method needs to be placed in the Load
event of MainForm
. Once the InitializeButtons
method is in the Load
event of MainForm, run the application to view the three buttons, per what was specified in the setup of MainForm's modes. Notice a button does not show up for the 'Default' mode due to the settings of the mode. The buttons availability will dynamically be updated based on the click events or keyboard commands received. Try clicking on the "Add" button or pressing 'F1' and see how the "Edit" button disappears and the application no longer recognizes the 'F2' keystroke.
[image3_approach2.png]
Figure 4
The last remaining part is to handle the form mode changes. Since each form usually has a different purpose and function this implementation is form specific. An example implementation is shown in the code block below.
private void MainForm_FormModeChanged(FormModeChangedEventArgs e)
{
switch (e.NewFormMode)
{
case FormMode.Mode.Default:
break;
case FormMode.Mode.Add:
// Perform Add action...
break;
case FormMode.Mode.Edit:
// Perform Edit action...
break;
case FormMode.Mode.Exit:
// Exit the form, prompt to save changes, etc...
break;
default:
break;
}
}
This second approach gives the user a consistent feel all the while providing the efficiencies that allow the Developer to focus on functionality and worry less about how to implement both a UI and keyboard interface for the application.
One nice feature about this approach is that it gives a Developer nearly limitless options in terms of the different modes a form can have. All it takes is adding more modes to the enumeration and then using those modes in the inherited forms.
Conclusion
You have just walked through creating a keyboard interface for a Microsoft .NET Windows Form client using a few different approaches. The approaches, from the most basic and simplest, to the more robust and complicated ones, demonstrated how to use .NET controls, inheritance and events to achieve a consistent and efficient keyboard interface for the end user.
Comments
There are no comments yet. Be the first to comment!