A common requirement for desktop applications with multiple tabs is to allow the user to save their workspace so they can quickly get back to being productive. With the ComponentOne docking controls for WPF (C1DockControl), you can deliver a multi-tabbed user interface that supports docking and floating windows. The user can rearrange their workspace quite easily by dragging tabs and docking them to any other location. So what if you need to save the workspace for future runs of the application? In this blog post I describe usage of the Save and Load methods so you can easily preserve a docked layout of your WPF or Silverlight application. The code shown below is for a WPF application because it's more concise. But i've also included a Silverlight example which uses IsolatedStorage to persist the settings.
There are two parts to this example: saving and loading. First, we’ll look at how we can save the layout. The key method on the control is the Save method which returns a C1DockSettings instance. From here, the control does no more work for you; you must determine how and where you want to save these settings. The C1DockSettings class contains an XML element (XElement) at its Root property. You could open a file save picker and write to an XML file on the user’s machine, or you could store it yourself in isolated storage. What is best for your requirements? For example, let’s save the layout as a string to an application setting. I wrote about creating application settings in a previous post. They are great for persisting information between runs of your WPF application, such as the UI layout. The code snippet below uses the Save method, and writes the XML layout to an application setting we’ve named DockLayout. We’re calling this code in the Window_Closing event so that it saves the layout every time the user closes the application. You may prefer to call this code in a save button or after anytime the user rearranges the C1DockControl using the ViewChanged event.
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
// save the settings to an application setting
C1DockSettings settings = dockControl.Save(true);
var doc = new XDocument();
doc.Add(settings.Root);
Properties.Settings.Default.DockLayout = doc.ToString();
Properties.Settings.Default.Save();
}
An important thing to note here is that we are using the overloaded Save method by passing in a parameter. When we pass in true, it tells C1DockControl to store the names of each C1DockTabControl, C1DockGroup and C1DockTabItem in the XML file. This is necessary in order to reconstruct the control and move tabs around later. If you don’t pass in true, then you are responsible for reconstructing the tab layout yourself. In order for C1DockControl to properly name each inner control, you must provide a name in your markup. For instance, you should set the x:Name property on each tab item such as below:
<c1:C1DockControl x:Name="dockControl">
<c1:C1DockGroup x:Name="c1DockGroup1">
<c1:C1DockTabControl x:Name="c1DockTabControl1" Dock="Top">
<c1:C1DockTabItem x:Name="c1DockTabItem1" Header="Tab 1" TabShape="Sloped">
<!--Your Content Here-->
</c1:C1DockTabItem>
<c1:C1DockTabItem x:Name="c1DockTabItem2" Header="Tab 2" TabShape="Sloped">
<!--Your Content Here-->
</c1:C1DockTabItem>
</c1:C1DockTabControl>
<c1:C1DockTabControl x:Name="c1DockTabControl2">
<c1:C1DockTabItem x:Name="c1DockTabItem3" Header="Tab 3" TabShape="Sloped" >
<!--Your Content Here-->
</c1:C1DockTabItem>
</c1:C1DockTabControl>
</c1:C1DockGroup>
<c1:C1DockGroup x:Name="c1DockGroup2">
<c1:C1DockTabControl x:Name="c1DockTabControl3" Dock="Top" DockWidth="500" DockHeight="500" TabItemShape="Rounded">
<c1:C1DockTabItem x:Name="c1DockTabItem4" Header="Tab 4">
<!--Your Content Here-->
</c1:C1DockTabItem>
<c1:C1DockTabItem x:Name="c1DockTabItem5" Header="Tab 5">
<!--Your Content Here-->
</c1:C1DockTabItem>
</c1:C1DockTabControl>
<c1:C1DockTabControl>
<c1:C1DockTabItem x:Name="c1DockTabItem6" Header="Tab 6">
<!--Your Content Here-->
</c1:C1DockTabItem>
</c1:C1DockTabControl>
</c1:C1DockGroup>
<c1:C1DockTabControl x:Name="c1DockTabControl4">
<c1:C1DockTabItem x:Name="c1DockTabItem7" Header="Tab 7" TabShape="Sloped">
<!--Your Content Here-->
</c1:C1DockTabItem>
<c1:C1DockTabItem x:Name="c1DockTabItem8" Header="Tab 8" TabShape="Sloped">
<!--Your Content Here-->
</c1:C1DockTabItem>
</c1:C1DockTabControl>
</c1:C1DockControl>
If you debug and step through the C1DockSettings code, you’ll see how the names of each element are saved along with the settings. Now the layout is saved and next we need to reload the settings the next time the application runs.
To load an existing layout you call the Load method. But first, we must obtain the layout string from our application settings and parse that into an XElement. You can call this code in your window’s constructor or in a load button.
// Re-configure the dockControl from the settings
if(!String.IsNullOrEmpty(Properties.Settings.Default.DockLayout))
{
XDocument doc = XDocument.Parse(Properties.Settings.Default.DockLayout);
dockControl.Load(doc.Root, true);
}
Again, you must pass in true as the second parameter to allow the C1DockControl to preserve each tab item so they do not need to be re-created.
Preserving the layout of C1DockControl between runs of your application is very easy when using the Save and Load methods. Remember, that these methods are used primarily to construct a C1DockSettings class that you can take and save however you want. In the attached sample we use an application setting, but you can customize this further to provide a different experience for saving and re-loading layouts. You can even save tabs in a floating state! If you need more control over how the tabs are saved in C1DockSettings, take a look at the VSDev9 sample included in Studio for Silverlight.