This is a migrated thread and some comments may be shown as answers.

Using SaveLaout and LoadLayout when binding to a collection using RadDocking.PanesSource

1 Answer 281 Views
Docking
This is a migrated thread and some comments may be shown as answers.
Eric
Top achievements
Rank 1
Eric asked on 21 Jul 2015, 05:03 PM

I am trying to define the panes for the RadDocking control using its PaneSource binding to an ObservableCollection in a ViewModel and use the SaveLayout and LoadLayout methods to persist the layout of the RadDocking control between sessions. The first question I guess I have is “Should this work?” Does the RadDockingPanesSource and the Save and Load layout methods work together? Below are the important pieces I have created trying to put this together.

 

<telerik:RadDocking x:Name=”radDocking” PanesSource=”{Binding Panes}”>
 
    <telerik:RadSplitContainer InitialPosition=”DockedLeft”>
        <telerik:RadPaneGroup x:Name=”LeftPaneGroup” />
    </telerik:RadSplitContainer >
 
    <telerik:RadSplitContainer InitialPosition=”DockedRight”>
        <telerik:RadPaneGroup x:Name=”RightPaneGroup” />
    </telerik:RadSplitContainer >
 
    <telerik:RadSplitContainer InitialPosition=”DockedBottom”>
        <telerik:RadPaneGroup x:Name=”BottomPaneGroup” />
    </telerik:RadSplitContainer >
 
    <telerik:RadDocking.DockingPanesFactory>
        <local:MyDockingPanesFactory />
    </telerik:RadDocking.DockingPanesFactory>
 
</telerik:RadDocking>

 The XAML above shows the RadDocking control in my MainWindow.xaml file.

public class MyDockingPanesFactory : DockingPanesFactory {
 
    protected override void AddPane(RadDocking radDocking, RadPane radPane) {
        RadDocking.SetSerializationTag(radPane, ((DockPaneViewModel)radPane.DataContext).SerializationTag);
 
        RadPaneGroup leftPaneGroup = radDocking.SplitItems.ToList().FirstOrDefault(i => i.Control.Name == “LeftPaneGroup”) as RadPaneGroup;
        if (leftPaneGroup != null) {
            if ((DockPaneViewModel)radPane.DataContext).InitialLocation == DockLocation.DockLeft) {
                leftPaneGroup.Items.Add(radPane);
            }
        }
 
        RadPaneGroup rightPaneGroup = radDocking.SplitItems.ToList().FirstOrDefault(i => i.Control.Name == “RightPaneGroup”) as RadPaneGroup;
        if (rightPaneGroup != null) {
            if ((DockPaneViewModel)radPane.DataContext).InitialLocation == DockLocation.DockRight) {
                rightPaneGroup.Items.Add(radPane);
            }
        }
 
 
        RadPaneGroup bottomPaneGroup = radDocking.SplitItems.ToList().FirstOrDefault(i => i.Control.Name == “BottomPaneGroup”) as RadPaneGroup;
        if (bottomPaneGroup != null) {
            if ((DockPaneViewModel)radPane.DataContext).InitialLocation == DockLocation.DockBottom) {
                bottomPaneGroup.Items.Add(radPane);
            }
        }
    }
 
    protected override RadPane CreateRadPaneForItem(object item) {
        if (item is DockPaneViewModel) {
            return new RadPane() {
              DataContext = item,
              Content = item
            };
        } else {
            throw new ArgumentException(“Invalid Type”);
        }
    }
 
}
 

The code above shows my DockingPaneFactory class and how is initially positions my ViewModels as RadPane(s). Notice in CreateRadPaneForItem I am setting the DataContext and the Content of the RadPane to the current DockPaneViewModel in the PanesSource. Setting the Content to the ViewModel will allow binding using a DataTemplate with a DataType defined (this is not shown). My DockPaneViewModel defines several notifiable properties like Header, IsActive, IsHidden, IsPinned, CanDockInDocumentHost, SerializationTag and defines its own InitialLocaiton that is used by the AddPane method in the DockingPaneFactory (above).

 

public abstract class DockPaneViewModel : ViewModelBase {
    //Header, IsActive, IsHidden, IsPinned, CanDockInDocumentHost, SerializationTag
}

 The code above is my DockPaneViewModel and which notifiable properties I have defined. These properties are bound their equivalent properties in the RadPane using a Style.

 

<Application … >
    <Application.Resources>
        <ResourceDictionary>
            <Style TargetType=”{x:Type telerik:RadPane}” BasedOn=”{StaticResource RadPaneStyle}”>
                <Setter Property=”Header” Value=”{Bindign Header, Mode=TwoWay}” />
                <Setter Property=”IsActive” Value=”{Bindign IsActive, Mode=TwoWay}” />
                <Setter Property=”IsHidden” Value=”{Bindign IsHidden, Mode=TwoWay}” />
                <Setter Property=”IsPinned” Value=”{Bindign IsPinned, Mode=TwoWay}” />
                <Setter Property=”CanDockInDocumentHost” Value=”{Bindign CanDockInDocumentHost, Mode=TwoWay}” />
            </Style>
        </ResourceDictionary>
    </Application.Resources>
</Application>

The code above shows the custom RadPaneStyle that binds the properties in my DockPaneViewModel to the RadPane. This Style is defined in the Application Resources.

private void Application_Startup(object sender, StartupEventArgs e){
    MainWindow mainWindow = new MainWindow();
    mainWindow.DataContext = new MainWindowViewModel();
    mainWindow.Show();
}

 The code above shows how I am creating and showing the MainWindow and setting its DataContext.

public class MainWindowViewModel : ViewModelBase {
 
    public ObservableCollection<DockPaneViewModel> Panes { get; private set; }
 
    public MainWindowViewModel(){
        Panes = new ObservableCollection<DockPaneViewModel>(
            new ViewModel1(),new ViewModel2(),new ViewModel3()
        );
    }
}

 The code above is my MainWindowViewModel which shows the ObservableColleciton of DockViewModels. I am just showing a hard-coded set of three ViewModels being added to the Panes collection. In the real project I am using an IOC container to create all classes that inherit from DockPaneViewModel which get added to the Panes collection.

private void RadRibbonWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
    using (System.IO.Stream stream = new System.IO.FileStream(App.LayoutSettings, System.IO.FileMode.Create)){
        this.radDocking.SaveLayout(stream);
    }
}
 
protected override void OnSourceInitialized(EventArgs e) {
    using (System.IO.Stream stream = new System.IO.FileStream(App.LayoutSettings, System.IO.FileMode.Open)) {
        this.radDocking.LoadLayout(stream);
    }
}

 Lastly the code above shows closing event and the override for the OnSourceInitialized. This is where I am saving and loading the layout XML file for the RadDocking control. The XML file that gets created does not seem to have any IsPinned properties. At first I thought I forgot to add IsPinned as a notifiable property to the DockPaneViewModel but that was not the case. I also thought I forgot to add IsPinned to the XAML Style that binds the properties between the RadPane and the DockPaneViewModel but that was not the case either.

I am not exactly sure why this is not working. I am not 100% sure if the RadDocking.PanesSource can be used with the SaveLayout and LoadLayout methods. If anyone could give me some direction or advice it would be greatly appreciated. I am not sure what to try next.

1 Answer, 1 is accepted

Sort by
0
Vladi
Telerik team
answered on 24 Jul 2015, 01:17 PM
Hi Eric,

Thank you for contacting us.

The RadDocking control's Save/LoadLayout and PanesSource features are designed to work as expected when used together. The difference between using the Save/LoadLayout with or without a PanesSource implementation is that when there is a PanesSource there could be conflicts with which objects are the up to date, those in the layout or the PanesSource collection. When a previously saved layout is loaded it will cause the control to disregard all of the instances in its PanesSource and load the instances from provided saved layout's xml. When this happens what should be done is to implement a custom logic that will find any conflict between the saved layout and the current PanesSource and manually resolve them by either adding a missing instance to the bound collection for the PanesSource or remove an instance.This approach has been showcased in our "PaneSourceWithLayout" example located in our GitHub repository here.

Regarding your questions about the IsPinned property not being saved in the generated xml this is indeed true and is done by design. By default the RadDocking control’s SaveLayout functionality persists only the most important properties (for the layout restoration) into the generated xml. When more "advanced" properties like the IsPinned, Header etc. need to be persisted you will need to make sure a unique SerializationTag is set. More details about this can be found in our online documentation here. When using bindings for those "advanced" properties the value of the bindings are not saved as they should come from the ViewModel itself. Saving the actual values of the bindings could lead to binding expression conflicts as the control cannot know which value from the layout or the binging should be used. If we think of the RadDocking control to be the View in the MVVM pattern, it should only know information of its UI (Pane positions, sizes etc.) and the ViewModel should provide the information about all of the bindings values. When loading the layout of a RadDocking control you will need to make sure that the ViewModel contains all of the up-to date values in order for them to be correctly resolved.

Hope this information is helpful.


Regards,
Vladi
Telerik
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
Tags
Docking
Asked by
Eric
Top achievements
Rank 1
Answers by
Vladi
Telerik team
Share this question
or