Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Tabbed MDI in WPF

0.00/5 (No votes)
9 Jan 2009 1  
How to use the TabControl to simulate MDI behavior in WPF.

Introduction

Microsoft is trying to kill support for MDI. In certain kinds of applications where many child forms need to be managed independently, it's really necessary to have MDI support. Microsoft is encouraging developers to move to other alternative approaches to MDI: Single Document Interface (SDI), Tabbed Workspaces (like IE7 and Firefox), or the navigational model. Since MDI is discouraged, WPF has no support for this. But in this article, I am going to show how we can use the WPF TabControl to get the flavor of MDI.

Basic Idea

The way I’m going to present the MDI behavior will not be like in a full-fledged MDI form. But, this concept will allow you to put your content in a multi-tabbed manner in a TabControl. Your WPF project will have a window. In that window, you’ll add a TabControl. This TabControl will host your pages (for this scenario, you need to develop a user control rather than a page) as tab items. This concept is shown in Figure 1.

Figure 1: MDI behavior with Tab control

In this scenario, you’ll create a user control (i.e., the MDI child) for your pages. It’s because you can’t host pages inside the TabControl’s tab item. Lets say you need to develop a few user controls for a system, say a student management system. The user controls may be ucStudentEnrollment, ucCourseAssignment etc. Now, when the user clicks on a menu like “Student Enrollment”, you’ll create an instance of ucStudentEnrollment, add a tab item to the TabControl, and then set the tab item’s Content property to the instance of ucStudentEnrollment.

Now, we need to have a way to make the window control (the MDI parent) interact with its child user controls (MDI children). Let’s have a scenario. When the user clicks on the close button in a user control (MDI child) which is hosted in a window (MDI parent), the user control (MDI child) needs to be removed from the window (MDI parent). The close event will be fired from the user control (MDI child), but the action will be handled by the window (MDI parent). So, there’s an interaction required between the children (User Controls) and the parent (Window). Also, the window needs to manage its children, such as keep track of if a child page is already open (here in this example, we’ll assume that a single form can’t be opened multiple times in the parent window).

Implementation Details

Each control has to implement an interface, and the MDI parent will interact with its children using an interface. The interface signature in this example is shown below:

public interface ITabbedMDI
{
    /// <summary>
    /// This event will be fired from user control and will be listened
    /// by parent MDI form. when this event will be fired from child control
    /// parent will close the form
    /// </summary>

    event delClosed CloseInitiated;

    /// <summary>
    /// This is unique name for the control. This will be used in dictonary object
    /// to keep track of the opened user control in parent form.
    /// </summary>

    string UniqueTabName{get;}

    /// <summary>
    /// This is the tile will be shown in the tile when this control
    /// will be show in parent
    /// </summary>

    string Title { get; }

}

In the above interface, the delClosed delegate has the following signature:

public delegate void delClosed(ITabbedMDI sender, EventArgs e);

This delegate defines the signature of the close event fired from user controls (MDI children). UniqueTabName is the unique name for the user control. The MDI parent will keep track of which child is currently open, by adding the unique tab name to a dictionary. So whenever a user tries to add a control, the MDI parent will check the dictionary to see whether the control unique name is already in the dictionary. If it is in the dictionary, the MDI parent will not add that control, rather set focus on the tab item where the control is hosted. The title in the interface is the tab tile.

Since each user control will implement the interface, the MDI parent can easily access the common properties of all user controls (e.g., title, unique name, event etc.) by casting it to the interface. When an MDI child is added to the MDI parent, there are a few things to be done:

  1. Create a new instance of the user control (for simplicity, assume that the user control is not already added). Since ucTab1 has implemented the interface ITabbedMDI, the user control will have two properties and an event will be available to the MDI parent.
  2. The MDI parent has a dictionary object of opened controls’ unique name/title pair. So, check if the user control is already opened by checking its unique name in that dictionary.
  3. If a user control exists in the dictionary, then set focus on the tab where the user control is hosted, and return without going to the next steps. If the user control unique name does not exist in the dictionary, then go to the next step.
  4. Create a new TabItem and add that TabItem to TabControl.
  5. Set the TabItem’s Name to the User Control’s UniqueName and set the TabItem’s Title to the User Control’s Header.
  6. Add the user control’s unique name and title pair in the dictionary maintained by the MDI parent for keeping track of the children.

Interface Implementation in the User Control (MDI Child)

Each user control will implement a single interface so that the MDI parent can manage these user controls (MDI children) in an unique manner. In a user control, the interface ITabbedMDI can be implemented as shown below:

#region ITabbedMDI Members

    /// <summary>
    /// This event will be fired when user will click close button
    /// </summary>

    public event delClosed CloseInitiated;

    /// <summary>
    /// This is unique name of the tab
    /// </summary>

    public string UniqueTabName
    {
      get
      {
            return "Tab2";
      }
    }

    /// <summary>
    /// This is the title that will be shown in the tab.
    /// </summary>

    public string Title
    {
       get { return "Tab 2 Title"; }
    }

#endregion

In the same user control, we can fire an event in the button close event as shown below:

private void btnClose_Click(object sender, RoutedEventArgs e)
{
    if (CloseInitiated != null)
    {
        CloseInitiated(this, new EventArgs());
    }
}

Window (MDI Parent)'s Interaction with MDI Children

When the user clicks on a menu item for a child form, the parent window creates a child instance as shown below:

/// <summary>
/// Create tab1 if not exists or set focus if exist
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>

private void mnuTab1_Click(object sender, RoutedEventArgs e)
{
    ucTab1 mdiChild = new ucTab1();
    AddTab(mdiChild);
}

The AddTab method will check if the tab is already open by checking the user control’s unique name in the opened children name list. If the tab is already open, then the AddTab method sets the focus on that tab. If the user control is not found, then the control is added to the tab.

/// <summary>
/// Add tab item to the tab
/// </summary>
/// <param name="mdiChild">This is the user control</param>

private void AddTab(ITabbedMDI mdiChild)
{
     //Check if the user control is already opened
     if (_mdiChildren.ContainsKey(mdiChild.UniqueTabName))
     {
        //user control is already opened in tab. 
        //So set focus to the tab item where the control hosted
        foreach (object item in tcMdi.Items)
        {
           TabItem ti = (TabItem)item;
           if (ti.Name == mdiChild.UniqueTabName)
           {
               ti.Focus();
               break;
           }
        }
     }
     else
     {
         //the control is not open in the tab item
         tcMdi.Visibility = Visibility.Visible;
         tcMdi.Width = this.ActualWidth;
         tcMdi.Height = this.ActualHeight;

         ((ITabbedMDI)mdiChild).CloseInitiated += new delClosed(CloseTab);

         //create a new tab item
         TabItem ti = new TabItem();

         //set the tab item's name to mdi child's unique name
         ti.Name = ((ITabbedMDI)mdiChild).UniqueTabName;

         //set the tab item's title to mdi child's title
         ti.Header = ((ITabbedMDI)mdiChild).Title;

         //set the content property of the tab item to mdi child
         ti.Content = mdiChild;
         ti.HorizontalContentAlignment = HorizontalAlignment.Stretch;
         ti.VerticalContentAlignment = VerticalAlignment.Top;

         //add the tab item to tab control
         tcMdi.Items.Add(ti);

         //set this tab as selected
         tcMdi.SelectedItem = ti;

         //add the mdi child's unique name in the open children's name list
         _mdiChildren.Add(((ITabbedMDI)mdiChild).UniqueTabName, 
                          ((ITabbedMDI)mdiChild).Title);

    }
}

Conclusion

Although Microsoft is discouraging MDI, we need a way to manage child forms inside a parent. MDI is popular as we can see in IE7 and Firefox. Also, we need to use different threads to mange each child form’s activities so that when the user initiates an action from a child form other children does not become irresponsive.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here