Click here to Skip to main content
Click here to Skip to main content

Tabbed MDI in WPF

, 9 Jan 2009
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sohel_Rana
Architect ImpleVista Aps
Denmark Denmark
Sohel has more than six years of experience in professional software development with extensive involvement in Web based Object-Oriented, Multi-Tiered application design and development. He's Familiar with Test Driven Development (TDD) and refactoring techniques as well as having expertise in architecturing large enterprise applications. He has Experience in working with Content Management System and Portal Management System tools like SharePoint, DotNetNuke, Ektron.
 
Over last few years, he’s involved in development with projects on Microsoft SharePoint and received Microsoft MVP for SharePoint Server Development in the year 2011 and 2012. Currently he's working in a software company located Copenhagen,Denmark on a project integrating SharePoint and SAP. You can read his popular blog at: http://ranaictiu-technicalblog.blogspot.com

Comments and Discussions

 
QuestionHow to Add Close button in all Tab Pinmemberavisatna18-Nov-12 20:49 
GeneralMy vote of 4 PinmemberRahul from New Delhi1-Nov-12 8:04 
GeneralBest way to Use MDI Windows in WPF PinmemberKPRAN12-Jul-12 20:30 
QuestionBest Example Pinmembernhasmita6-Dec-11 21:19 
GeneralMy vote of 4 Pinmembernhasmita6-Dec-11 21:15 
GeneralGreat Job ! Pinmembergilbertblanco12-Nov-10 19:22 
GeneralMy vote of 5 Pingroupsathyabala.s4-Oct-10 23:01 
great work
GeneralMy vote of 4 Pinmemberatos hariom2-Sep-10 0:50 
GeneralMy vote of 1 PinmemberRredCat15-Jan-09 2:32 

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

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

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 9 Jan 2009
Article Copyright 2009 by Sohel_Rana
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid