Architect or Cobbler?
Good code starts with good design

MDI - or not

Wednesday, November 15, 2006

Today I thought I'd do the windows, and then I found out that WPF doesn't support MDI (actually I knew it didn't, I was hoping that somehow MDI had sneaked into RTM). I could of course just use Windows Forms Integration and simply drag a Windows Forms Window in and use that through Forms interop, but I really wanted a XAML only solution. The alternative is to create your own window manager and to do it that way, but that's just way too much work :-)

So I've decided to use the TabControl, after all it's topical seeing as IE7 is out with it's own tabs, so that's what we're going to do in this session - hook up a TabControl to the form and then make it respond to the Window Open and Window Close menu commands.

Firstly I've had to modify the menu, as Window tile and cascade make no sense with a tab option. So I've altered the XAML so that my Window menu is now:

<Menu Background="White">
     <MenuItem Header="Window"  >
          <MenuItem Header="New Window" Click="NewTab"/>
          <MenuItem Header ="Close Window" Click="CloseTab" />    
     </MenuItem>
</Menu>

Notice I've hooked the click events to the methods NewTab and CloseTab, we won't implement them yet, as I need to add the TabControl to my XAML at this point. I'm going to add it to the MainWindow DockPanel that was created earlier, and it looks something like this:

 <DockPanel Name="DockPanel_MainDisplay"
            Background="DarkGray"
            Grid.Row="2">
    <TabControl Name="MainWindow_TabPanel" TabStripPlacement="Top">
       <TabItem Header="Window1">
       </TabItem> 
    </TabControl>
</DockPanel>

A TabControl is simply a collection of TabItems. Each TabItem has a header which is it's title, and some visual content - for now we'll simply leave the visual content out.

If you build and run the application, then you'll see something like this:

Now, to hook the events up, you need to implement the NewTab and CloseTab methods in the code behind file Scribble.xaml.cs. The logic is fairly straightforward, I create a new TabItem and then I add it to the TabControl's Items collection. The TabControl is visible to me because I gave it a Name attribute in the XAML. I then grab the focus, and that's it.

If you do significant actions with a RoutedEvent such as changing the layout or drawing to screen, you should indicate this by setting the Handled attribute to true. This prevents any other handler between you and the root node from dealing with the event.

The CloseTab functionality is equally straightforward, simply remove the selected TabItem from the Items collection (I've also made some logic to rename the tabs, it's probably not necessary, I just wanted to see if it worked)

     void NewTab(object sender, RoutedEventArgs e)
     {
            WindowCount++;
            TabItem item = new TabItem();
            item.Header="Window"+WindowCount.ToString();
            MainWindow_TabPanel.Items.Add(item);
            item.Focus();
            e.Handled = true;
     }


     void CloseTab(object sender, RoutedEventArgs e)
     {
            MainWindow_TabPanel.Items.Remove(MainWindow_TabPanel.SelectedItem);
            WindowCount--;
            // because of my simple naming scheme I need to rename the tabs
            RenameTabs();
            e.Handled = true;
      }

You can build and run , and you can now add and remove tabs to the application. Of course you could (if you weren't as lazy as me) add a context sensitive menu to each tab to add and close tabs.

So far this isn't very exciting, I still can't scribble on the application, but before I went messing around creating mouse handlers, I noticed there was this Canvas class called the InkCanvas, so I wondered what functionality it offered.

So I simply added it to the XAML hierarchy, and bingo I got full Scribble functionality. I was that excited I could have kissed someone from the WPF team (except Rob, I've met him and the moustache would tickle). To add the functionality involved 2 changes, one to the XAML and one to the code as outlined below:

 <TabControl Name="MainWindow_TabPanel" TabStripPlacement="Top">
       <TabItem Header="Window1">
          <InkCanvas />
       </TabItem> 
  </TabControl>
...item.Content = new InkCanvas();

My GUI is very nearly complete, I need functionality for Pen Thicknesses, and I may even add Pen Colour as well, just to really push the boat out, but that will have to wait till tomorrow, for now here's my Scribble app doing its stuff.

You can download my VS2005 project here.


# posted by James @ 6:47 PM   2 comments Comments: I tried out your app...and I noticed that the menus were acting strange. I thought it was a WPF bug, but a Dev on our team thinks that you used seperate Menu elements for each menu.

While that might seem like the right thing to do, you should just have one "Menu" and have menuitems as children for File, Edit, etc...

Happy to read of your experiences.

Thanks, Rob Relyea
Program Manager, WPF Team
http://rrelyea.spaces.live.com
# posted by Blogger Rob Relyea @ 10:34 PM   Thanks Rob, I fixed it. Out of interest what was the problem that identified this?
# posted by Blogger James @ 10:47 AM   Post a Comment

<< Main blog page
This page is powered by Blogger. Isn't yours?