Quite often the users might want to save and then restore the layout they have previously left when closing an application that uses any kind of docking layout management. The powerful Save/Load layout mechanism of Telerik RadDocking enables such users to accomplish this task in an absolutely straight-forward manner. The developers building the application can use the advanced API of the control and implement the needed logic with just a few lines of code.

The example below demonstrates how to persist the panes’ position in a Weather Forecast Browser built specifically for this blog post.

This is the first of series of blog posts dedicated on creating a Weather Forecast Browser application that will support Save/Load layout.

Steps to create the Weather Forecast Browser application

  1. Create the layout of the application
    • Build the layout
    • Make the layout Save/Load enabled
    • Create save and load buttons and implement the corresponding actions
    • Create a menu with the stored layouts that will restore the selected layout
    • Save all the items of the menu to the isolated storage
  2. In the next blog post I will add custom parts to the application
    • Create the UI of the parts
    • Implement the View-Models of the parts
    • Implement consuming services

The code snippets and descriptions below discuss on point 1

  1. First of all, you need to add references to Telerik.Windows.Controls, Telerik.Windows.Controls.Navigation and Telerik.Windows.Controls.Docking assemblies of your project to be able to use the RadDocking control.References 
  2. Then, you can build the layout. For the purposes of this example you can arrange the panes as shown below:image
  3. Now you are ready to create the main page:
       1:  <UserControl x:Class="WeatherStation.Page"
       2:   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
       3:   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       4:   xmlns:telerikNavigation="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Navigation"
       5:   xmlns:telerikDocking="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Docking">
       6:   <Grid x:Name="LayoutRoot" Background="White">
       7:   <Grid.RowDefinitions>
       8:   <RowDefinition Height="Auto" />
       9:   <RowDefinition Height="*" />
      10:   </Grid.RowDefinitions> 
      11:   <TextBlock Grid.Row="0" Text="The menu comes here" />
      12:   <telerikDocking:RadDocking Grid.Row="1"> 
      13:   </telerikDocking:RadDocking>
      14:   </Grid>
      15:  </UserControl>
  4. To create the left-hand panel, you should add a docked-to-the-left split container holding the pane group , and a pane that will contain the Cities view:
       1:  <telerikDocking:RadSplitContainer InitialPosition="DockedLeft">
       2:   <telerikDocking:RadPaneGroup>
       3:   <telerikDocking:RadPane Header="Cities">
       4:   <TextBlock Text="Cities view comes here" />
       5:   </telerikDocking:RadPane>
       6:   </telerikDocking:RadPaneGroup>
       7:  </telerikDocking:RadSplitContainer>
  5. The right part containing the morning, noon and evening views will be built by a split container that is docked to the right and vertically oriented via its Orientation property set to Vertical, and three groups containing one pane each:
       1:  <telerikDocking:RadSplitContainer InitialPosition="DockedRight" Orientation="Vertical">
       2:   <telerikDocking:RadPaneGroup>
       3:   <telerikDocking:RadPane Header="Morning">
       4:   <TextBlock Text="Morning view comes here" />
       5:   </telerikDocking:RadPane>
       6:   </telerikDocking:RadPaneGroup>
       7:   
       8:   <telerikDocking:RadPaneGroup>
       9:   <telerikDocking:RadPane Header="Noon">
      10:   <TextBlock Text="Noon view comes here" />
      11:   </telerikDocking:RadPane>
      12:   </telerikDocking:RadPaneGroup>
      13:   
      14:   <telerikDocking:RadPaneGroup>
      15:   <telerikDocking:RadPane Header="Evening">
      16:   <TextBlock Text="Evening view comes here" />
      17:   </telerikDocking:RadPane>
      18:   </telerikDocking:RadPaneGroup>
      19:  </telerikDocking:RadSplitContainer>
  6. The center panel should fill the rest of the screen. To accomplish this task you will have to put the panel in the document host by creating a split container, putting a pane group in it and a single pane into the group:
       1:  <telerikDocking:RadDocking.DocumentHost>
       2:   <telerikDocking:RadSplitContainer>
       3:   <telerikDocking:RadPaneGroup>
       4:   <telerikDocking:RadPane Header="Main view">
       5:   <TextBlock Text="The main view comes here" />
       6:   </telerikDocking:RadPane>
       7:   </telerikDocking:RadPaneGroup>
       8:   </telerikDocking:RadSplitContainer>
       9:  </telerikDocking:RadDocking.DocumentHost>
  7. The result will be looking like: FirstResult
  8. Now, you should enable the Save/Load layout feature of RadDocking. You can accomplish this task by setting the attached SerializationTag property of RadDocking for all panes (you really don’t need to name the groups and split containers as they are just layout elements and don’t contain anything that is not owned by the docking control):
       1:  <telerikDocking:RadDocking Grid.Row="1">
       2:   <telerikDocking:RadSplitContainer InitialPosition="DockedLeft">
       3:   <telerikDocking:RadPaneGroup>
       4:   <telerikDocking:RadPane Header="Cities"
       5:   telerikDocking:RadDocking.SerializationTag="CitiesPane">
       6:   <TextBlock Text="Cities view comes here" />
       7:   </telerikDocking:RadPane>
       8:   </telerikDocking:RadPaneGroup>
       9:   </telerikDocking:RadSplitContainer>
      10:   
      11:   <telerikDocking:RadSplitContainer InitialPosition="DockedRight" Orientation="Vertical">
      12:   <telerikDocking:RadPaneGroup>
      13:   <telerikDocking:RadPane Header="Morning"
      14:   telerikDocking:RadDocking.SerializationTag="MorningPane">
      15:   <TextBlock Text="Morning view comes here" />
      16:   </telerikDocking:RadPane>
      17:   </telerikDocking:RadPaneGroup>
      18:   
      19:   <telerikDocking:RadPaneGroup>
      20:   <telerikDocking:RadPane Header="Noon"
      21:   telerikDocking:RadDocking.SerializationTag="NoonPane">
      22:   <TextBlock Text="Noon view comes here" />
      23:   </telerikDocking:RadPane>
      24:   </telerikDocking:RadPaneGroup>
      25:   
      26:   <telerikDocking:RadPaneGroup>
      27:   <telerikDocking:RadPane Header="Evening"
      28:   telerikDocking:RadDocking.SerializationTag="EveningPane">
      29:   <TextBlock Text="Evening view comes here" />
      30:   </telerikDocking:RadPane>
      31:   </telerikDocking:RadPaneGroup>
      32:   </telerikDocking:RadSplitContainer>
      33:   
      34:   <telerikDocking:RadDocking.DocumentHost>
      35:   <telerikDocking:RadSplitContainer>
      36:   <telerikDocking:RadPaneGroup>
      37:   <telerikDocking:RadPane Header="Main view"
      38:   telerikDocking:RadDocking.SerializationTag="MainPane">
      39:   <TextBlock Text="The main view comes here" />
      40:   </telerikDocking:RadPane>
      41:   </telerikDocking:RadPaneGroup>
      42:   </telerikDocking:RadSplitContainer>
      43:   </telerikDocking:RadDocking.DocumentHost>
      44:  </telerikDocking:RadDocking>
  9. Now that you have a fully customizable layout that is Save/Load layout enabled you need to include a tool to allow you save and then load the layout. You can put a RadMenu containing two items – “Save layout” and “Load layout”. The first one will save the layout in the memory, while the second will load it if it has been previously saved. First of all, you will need to add x:Name= “docking” attribute to the docking control as you will need to access its methods. You can replace the TextBlock that worked as a placeholder for the menu with the following code:
       1:  <telerikNavigation:RadMenu Grid.Row="0">
       2:   <telerikNavigation:RadMenuItem Header="Save layout" Click="RadMenuItemSave_Click" />
       3:   <telerikNavigation:RadMenuItem Header="Load layout" x:Name="LoadLayout" IsEnabled="False" Click="RadMenuItemLoad_Click" />
       4:  </telerikNavigation:RadMenu>
  10. And to implement the event handlers as follows:
       1:  private byte[] layoutBytes;
       2:   
       3:  private void RadMenuItemLoad_Click(object sender, RoutedEventArgs e)
       4:  {
       5:   using (MemoryStream stream = new MemoryStream(this.layoutBytes))
       6:      {
       7:   this.docking.LoadLayout(stream);
       8:      }
       9:  }
      10:   
      11:  private void RadMenuItemSave_Click(object sender, RoutedEventArgs e)
      12:  {
      13:   using (MemoryStream stream = new MemoryStream())
      14:      {
      15:   this.docking.SaveLayout(stream);
      16:   this.layoutBytes = stream.GetBuffer();
      17:   
      18:   this.LoadLayout.IsEnabled = true;
      19:      }
      20:  }
  11. In a few minutes and with a few lines of code you created a relatively complex and flexible layout that can be customized, saved and then restored. Now your layout can be saved and restored, however it can remember only the last state. You can enhance the application to:
    • Allow restoring to several previous states of the layout
    • Store the layouts to the local storage
  12. You can optimize the application’s architecture by adding a View-Model application and enhance the binding logic. You will need one View-Model class for the whole application. It exposes two properties - SavedLayouts, HasSavedLayouts.
    • SavedLayouts property is a collection of key/value pairs, that contain the stored layout data
    • HasSavedLayouts is a Boolean property indicating whether there are stored layouts
  13. You should bind the menu to the SavedLayouts property that is an observable collection of key-value pairs with key - string and value – array of bytes. Below is the XAML:
       1:  <telerikNavigation:RadMenu Grid.Row="0">
       2:   <telerikNavigation:RadMenuItem Header="Save layout" Click="RadMenuItemSave_Click" />
       3:   <telerikNavigation:RadMenuItem Header="Load layout" x:Name="LoadLayoutMenuItem" Click="RadMenuItemLoad_Click" IsEnabled="{Binding HasSavedLayouts}" ItemsSource="{Binding SavedLayouts}" DisplayMemberPath="Key" />
       4:   <telerikNavigation:RadMenuItem Header="Clear saved" Click="RadMenuItemClear_Click" />
       5:  </telerikNavigation:RadMenu>
  14. Then pass an instance of the View-Model class as data context of the page and load the already saved items from the isolated storage:
       1:  private ViewModels.WeatherStationViewModel viewModel;
       2:   
       3:  public Page()
       4:  {
       5:      InitializeComponent();
       6:   this.viewModel = newViewModels.WeatherStationViewModel();
       7:      IsolatedStoragePersister.LoadState(this.viewModel);
       8:   this.DataContext = this.viewModel;
       9:  }
  15. You will also have to change the event handlers of the menu items’ Click events:
       1:  private void RadMenuItemLoad_Click(object sender, RoutedEventArgs e)
       2:  {
       3:      var radArgs = e as RadRoutedEventArgs;
       4:   if (radArgs != null)
       5:      {
       6:          RadMenuItem item = radArgs.Source as RadMenuItem;
       7:   if (item != null && item.DataContext is KeyValuePair<string, byte[]>)
       8:          {
       9:   using (MemoryStream stream = new MemoryStream(((KeyValuePair<string, byte[]>)item.DataContext).Value))
      10:              {
      11:   this.docking.LoadLayout(stream);
      12:              }
      13:          }
      14:      }
      15:  }
      16:   
      17:  private void RadMenuItemSave_Click(object sender, RoutedEventArgs e)
      18:  {
      19:   using (MemoryStream stream = new MemoryStream())
      20:      {
      21:   this.docking.SaveLayout(stream);
      22:   byte[] layoutBytes = stream.GetBuffer();
      23:   
      24:   this.viewModel.SavedLayouts.Add(new KeyValuePair<string, byte[]>(args.PromptResult, layoutBytes));
      25:  IsolatedStoragePersister.SaveState(this.viewModel);
      26:  }
      27:  }
      28:   
      29:  private void RadMenuItemClear_Click(object sender, RoutedEventArgs e)
      30:  {
      31:   this.viewModel.SavedLayouts.Clear();
      32:      IsolatedStoragePersister.SaveState(this.viewModel);
      33:  }

As a result the application has its layout configured and you are ready to implement the actual functionality. This is going to be demonstrated in the next blog post.

You can find the source code here.


Related Posts

Comments

Comments are disabled in preview mode.