How do I bind an ObservableCollection to an AvalonDock DocumentPaneGroup? - c#

I need to load a collection of items as documents in AvalonDock 2.0. These objects inherit from an abstract class, for which I want to render a frame inside the document depending on which subclass are.
This is my XAML:
<ad:DockingManager Background="Gray" DocumentsSource="{Binding Path=OpenProjects}"
ActiveContent="{Binding Path=CurrentProject, Mode=TwoWay}">
<ad:DockingManager.DocumentHeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=OpenProjects/Name}" />
</DataTemplate>
</ad:DockingManager.DocumentHeaderTemplate>
<ad:DockingManager.LayoutItemTemplate>
<DataTemplate>
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:SubclassAViewModel}">
<Frame Source="Pages/SubclassAProject.xaml" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SubclassBViewModel}">
<Frame Source="Pages/SubclassBProject.xaml" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SubclassCViewModel}">
<Frame Source="Pages/SubclassCProject.xaml" />
</DataTemplate>
</Grid.Resources>
</Grid>
</DataTemplate>
</ad:DockingManager.LayoutItemTemplate>
<ad:LayoutRoot>
<ad:LayoutPanel>
<ad:LayoutDocumentPaneGroup>
<ad:LayoutDocumentPane>
</ad:LayoutDocumentPane>
</ad:LayoutDocumentPaneGroup>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
So far I've achieved to show as many documents as items are in the OpenProjects collection, but I can't seem to show anything inside each document.
Plus, I don't know if I'm using ActiveContent properly: I want to assign to CurrentProject the ViewModel assigned on the current active document.
Thank you for your time.

The reason why you are not able to see any content is of the way you defined your LayoutItem templates. This can't work.
Also consider to use a custom control instead of the Frame. The Frame is very heavy. Unless you don't need to display HTML, avoid this control. Content navigation is very easy to implement, in case you want to show navigable content. Just wrap your document content into a UserControl.
You are using the ActiveContent property properly.
To fix your problem you have three recommended solutions, where the first doesn't exactly meet your requirements. Since you defined the DockingManager.LayoutItemTemplate wrong, I will show it anyway.
Solution 1: Local LayoutItemTemplate
In case you only need a single template for all LayoutDocument and LayoutAnchorable containers, you can use the DockingManager.LayoutItemTemplate property. This property accepts a single DataTemplate. Nested DataTemplate definitions, like in your code, are generally not supported by WPF.
<ad:DockingManager>
<ad:DockingManager.LayoutItemTemplate>
<DataTemplate>
<Frame Source="Pages/SubclassAProject.xaml" />
</DataTemplate>
</ad:DockingManager.LayoutItemTemplate>
<ad:LayoutRoot>
<ad:LayoutPanel>
<ad:LayoutDocumentPaneGroup>
<ad:LayoutDocumentPane />
</ad:LayoutDocumentPaneGroup>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
Solution 2: Implicit DataTemplate
In more advanced scenarios you display different views based on different models. If the displayed content depends on the data type of the model alone (like in your case), the recommended approach is to provide implicit DataTemplate definitions.
WPF will automatically apply an implicit DataTemplate to every data type that matches the DataTemplate.TargetType of this template.
The DataTemplate is implicit, if it has no explicit x:Key value assigned. To ensure that the DataTemplate can actually be applied automatically, the DataTemplate must also be defined in the same resource scope as the target type. E.g., defining the DataTemplate in Application.Resources of App.xaml, would make the template to be applied automatically in the application scope.
<ad:DockingManager>
<ad:DockingManager.Resources>
<DataTemplate DataType="{x:Type vm:SubclassAViewModel}">
<Frame Source="Pages/SubclassAProject.xaml" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SubclassBViewModel}">
<Frame Source="Pages/SubclassBProject.xaml" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SubclassCViewModel}">
<Frame Source="Pages/SubclassCProject.xaml" />
</DataTemplate>
</ad:DockingManager.Resources>
<ad:LayoutRoot>
<ad:LayoutPanel>
<ad:LayoutDocumentPaneGroup>
<ad:LayoutDocumentPane>
</ad:LayoutDocumentPane>
</ad:LayoutDocumentPaneGroup>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
Solution 3: DataTemplateSelector
The previous solution, which uses implicit DataTemplate definitions, can be replaced with a DataTemplateSelector. DataTemplateSelector is another WPF concept to apply a DataTemplate selectively.
A DataTemplateSelector is the recommended choice, if selecting a DataTemplate may depend on more complex constraints, than the model's data type alone. It allows to e.g. evaluate the data item and chose the appropriate template based on certain criteria.
To define a template selector, you have to extend DataTemplateSelector, which is expected to return a DataTemplate.
The easiest way is to define the templates in App.xaml resource dictionary using an x:Key and then select from them based on the condition.
DockingManger accepts a template selector by assigning the DockingManager.LayoutItemTemplateSelector property:
App.xaml
Define the explicit DataTemplate using x:Key:
<Application.Resources>
<DataTemplate x:Key="SubclassAViewModelTemplate" DataType="{x:Type vm:SubclassAViewModel}">
<Frame Source="Pages/SubclassAProject.xaml" />
</DataTemplate>
<DataTemplate x:Key="SubclassBViewModelTemplate" DataType="{x:Type vm:SubclassBViewModel}">
<Frame Source="Pages/SubclassBProject.xaml" />
</DataTemplate>
<DataTemplate x:Key="SubclassCViewModelTemplate" DataType="{x:Type vm:SubclassCViewModel}">
<Frame Source="Pages/SubclassCProject.xaml" />
</DataTemplate>
</Application.Resources>
DocumentManagerTemplateSelector.cs
The following code uses the Switch Expression, which is available since C# 8.0. It can be replaced with a switch statement or cascaded if-statements.
class DocumentManagerTemplateSelector : DataTemplateSelector
{
#region Overrides of DataTemplateSelector
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return item switch
{
SubclassAViewModel _ => Application.Current.Resources["SubclassAViewModelTemplate"] as DataTemplate,
SubclassBViewModel _ => Application.Current.Resources["SubclassBViewModelTemplate"] as DataTemplate,
SubclassCViewModel _ => Application.Current.Resources["SubclassCViewModelTemplate"] as DataTemplate,
_ => base.SelectTemplate(item, container)
};
}
#endregion
}
MainWindow.xaml
<ad:DockingManager>
<xcad:DockingManager.LayoutItemTemplateSelector>
<local:DocumentManagerTemplateSelector />
</xcad:DockingManager.LayoutItemTemplateSelector>
<ad:LayoutRoot>
<ad:LayoutPanel>
<ad:LayoutDocumentPaneGroup>
<ad:LayoutDocumentPane>
</ad:LayoutDocumentPane>
</ad:LayoutDocumentPaneGroup>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>

Related

How to dynamically generate grids from a variety of templates

I am relatively new to WPF and I'm trying to create a system where you can click a button to add a new tab which generates a corresponding grid to display content. For example, when you open a new tab, the first page would be "Home page", you then have options within this new tab to navigate to other content such as "Options". You can then add another tab and repeat the process etc. IT would be like Google Chrome how each tab represents another browser experience.
So far I have my tabs working, however I am stuck with how I am supposed to generate grids for each tab. Does anyone have any suggestions on how I could do this?
Create an abstract TabData base class with a Header string property.
Create a class that derives from TabData for each type of tab that you want (e.g. HomeData, OptionsData, etc.)
Create a view model with an Items property of type IEnumerable<TabData>.
Bind the ItemsSource property of the TabControl to Items and define an implicit DataTemplate for each concrete TabData type:
<TabControl ItemsSource="{Binding Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<DataTemplate DataType="{x:Type local:HomeData}">
...
</DataTemplate>
<DataTemplate DataType="{x:Type local:OptionsData}">
...
</DataTemplate>
</TabControl.Resources>
</TabControl>
The DataTemplate defines the appearance of a tab and may contain a DataGrid or any other element.

WPF Ressource Dictionary match DataTemplate, Styles and Views for a Control depending of Type

I have problems with matching Ressource Dictionaries for a specific type in my project.
At start of my application my programm takes all .dll files in the folder and bind the .dlls to my project. After that I will search in all Assemblies for .xaml Files also the Ressource Dictionaries in my different dlls.
At the end I got a list with my Ressource Dictionaries.
Okay here is my problem now...How can I select the right dictionary for my type in the list View dynamicly. The second thing is how can I match the Dictionary Templates only to 1 control in my Window?
When the type of the items in my list view are changing check again for the right dictionary maybe i need another one
Here is an example of 1 dictionary
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PresentObjectDataTemplate">
<Style TargetType="ListView">
<!-- My Style for my ListView Control-->
</Style>
<!-- My Data Templates only for 1 Control-->
<DataTemplate DataType="{x:Type local:Entity1}">
<StackPanel>
<TextBox Text="{Binding A1}"/>
<TextBox Text="{Binding A2}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Entity2}">
<StackPanel>
<TextBox Text="{Binding A1}"/>
<TextBox Text="{Binding A2}"/>
</StackPanel>
</DataTemplate>
My MainWindow has 3 Controls
A treeView to select my List
A listview to show the objects in the list
A contentcontrol to show the properties of a selected object
When I merge the dictionary to the window ressources my list view and my content control will use the datatemplates. But i want that the templates only used in the contentcontrol
Here a picture from my idea for better understanding
myPicture

One ViewModel two Views with MVVMLight in WPF

The datacontext's are configured in App.xaml (with MVVMLight), but when is one ViewModel one View.
<DataTemplate DataType="{x:Type vm:VMUserControl}">
<views:UCViewSnow />
</DataTemplate>
I need use the same ViewModel with multiples Views, i added DataContext in codebehind for each View (.xaml.cs) but not working.
DataContext = ViewModel.ViewModelLocator.GetStaticVMuserControl;
I need to add DataContext according to parameters from app.config.
I use WPF with framework 4.5.1 with MVVM light from nuget in VS 2015
<DataTemplate DataType="{x:Type vm:VMUserControl}">
<views:UCViewDark />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:VMUserControl}">
<views:UCViewSnow/>
</DataTemplate>
I see couple potential reasons why DataTemplate can didn't work.
1st - probably you didn't bind Content property of your view with your DataContext (vm:VMUserControl).
2nd - you use custom template of content control without ContentPresenter.
For example:
App.xaml
...
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator" />
</Application.Resources>
...
In your View
Update:
In case if you define two datatemplate for one DataType (viewmodel)
you additional should implement and use own ContentTemplateSelector.
<View ...
DataSource={Binding Source={StaticResource Locator}, Path=YourViewModel}>
<View.Resource>
<ResourceDictionar>
<DataTemplate DataType="{x:Type vm:VMUserControl}"
x:Key="darkDataTemplate">
<views:UCViewDark />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:VMUserControl}"
x:Key="snowDataTemplate">
<views:UCViewSnow/>
</DataTemplate>
<local:CustomContentTemplateSelector x:Key="yourContentTemplateSelector"
FirstDataTemplate={StaticResource snowDataTemplate}
SecondDataTemplate={StaticResource darkDataTemplate} />
</ResourceDictionar>
</View.Resource>
<ContentControl Content={Binding}
ContentTemplateSelector={StaticResource yourContentTemplateSelector}/>
CustomContentTemplateSelector.cs
public class CustomContentTemplateSelector:DataTemplateSelector
{
public DataTemplate FirstDataTemplate {get; set;}
public DataTemplate SecondDataTemplate {get; set;}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container){
// here you should impelement logic for choose data template
// if (condition)
// return FirstDataTemplate
// else
// return SecondDataTemplate
}
}

Best way to bind a set of same-type ViewModels to a TabControl in MVVM / WPF

I have an existing ViewModel and View in an MVVM project. Effectively this View presents a collection of items in a particular, styled way. I'll call this existing ViewModel "CollectionPresenter".
Up to now, this has been presented as as follows in XAML:
<Grid>
<ns:CollectionPresenter />
</Grid>
Now, I want to have a dynamic collection of these "CollectionPresenter" view models made available ideally in a tab view.
My approach has been to define an observable collection of these "CollectionPresenters", creating them first on construction of the parent view model. The XAML above then changed to look something like this:
<TabControl ItemsSource="{TemplateBinding CollectionPresenters}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding CollectionPresenterTitle}">
</DataTemplate>
<TabControl.ItemTemplate>
<TabControl.ContentTemplate>
... this is where things get confusing
</TabControl.ContentTemplate>
<TabControl>
You can see above my problem is the ContentTemplate.
When I load this up, I get a tab control and it has as many tabs as my observable collection of "CollectionPresenter" objects.
However, the content of the tab control is always empty.
Is this approach correct - and is there a better way regardless?
EDIT: ADDING SOME EXTRA THINGS TO MAKE IT CLEARER
I've tried the below, but it doesn't work. The XAML with the Tab Control (the binding to "Things" works fine):
<TabControl ItemsSource="{TemplateBinding Things}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:Thing}">
<TextBlock Text="{Binding ThingName}" Width="200" Background="Blue" Foreground="White"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type viewModels:Thing}">
<TextBlock Text="{Binding ThingName}" Width="500" Height="500" Background="Blue" Foreground="White"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The definition for the "Things" observable collection (which is inside the templated parent (ParentObject) of the XAML with the tab control):
public static readonly DependencyProperty ThingsProperty =
DependencyProperty.Register("Things", typeof(ObservableCollection<Thing>), typeof(ParentObject), new PropertyMetadata(null));
public ObservableCollection<Thing> Things
{
get { return (ObservableCollection<Thing>)GetValue(ThingsProperty); }
set { SetValue(ThingsProperty, value); }
}
Stripped down version of the "Thing" view model:
public class Thing : ViewModelBase
{
public Thing()
{
}
public void Initialise(ObservableCollection<Thing> things, string thingName)
{
Things = things;
ThingName = thingName;
}
public static readonly DependencyProperty ThingNameProperty =
DependencyProperty.Register("ThingName", typeof(string), typeof(Thing), new PropertyMetadata(null));
public string ThingName
{
get { return (string)GetValue(ThingNameProperty); }
set { SetValue(ThingNameProperty, value); }
}
}
Looking at my answer to the WPF MVVM navigate views question, you can see this:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
<Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
<Views:CompanyView />
</DataTemplate>
Now, wherever we use an instance from one of these types in our application, these DataTemplates will tell the framework to display the related view instead.
Therefore, your solution is to simply not hard-code one single DataTemplate to the TabControl.ItemTemplate property, but to leave that blank instead. If you use multiple DataTemplates without providing x:Key values, then they will implicitly be applied when each data object is to be rendered in the TabControl.
UPDATE >>>
Using these DataTemplates should leave your TabControl looking like this:
<TabControl ItemsSource="{TemplateBinding Things}" />
I'm not sure why you're using a TemplateBinding there though as you don't need to define any new templates to get this working... therefore, you should be using a plain old Binding instead.
One other thing that you need to do is to use different data types for each item in the collection that you want to display differently. You could derive custom classes from your Thing class and so the collection could still be of type ObservableCollection<Thing>.

Don't create new view each time with DataTemplate/DataType

I have something like this:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<!-- View 1 Here -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<!-- View 2 here -->
</DataTemplate>
<Window.Resources>
<ContentPresenter Content="{Binding}"/>
</Window>
This will automatically swap out the view as I bind different viewmodels, which is very handy.
However, I have one view with a tabcontrol and many subviews. Each subview has several visual parts that are configured by a custom xml file (complex business case). Each time this view is created, the xml file is parsed which causes a small (1-2 second) delay. It's enough of a delay to be annoying and make the UI feel sluggish.
Is there a way to use the DataTemplate pattern without destroying and recreating the view each time a viewmodel is bound? I'd rather not change the viewmodel if possible.
For this case the easiest solution is to have the two views always there and change which one is visible. You can use a converter to change the visibility based on the type of the data context
<View1 Visibility="{Binding Converter={StaticResource TypeToVisibilityConverter, ConverterParameter=VM1}" />
<View2 Visibility="{Binding Converter={StaticResource TypeToVisibilityConverter, ConverterParameter=VM2}" />
And the converter will check if the type matches with the parameter to return Visible, or Collapsed otherwise.
You could wrap your VM into an additional class. Your DataTemplates will decide on the type of the Wrapper class but the real implementation will be exposer through a property of this Wrapper. When this property will change the DataTemplate wont be reloaded but all the bindings will be refreshed.
Wrapper class:
public class WrapperVM1:INotifyPropertyChanged
{
public Content VM1 { get{...} set{...} }
}
public class WrapperVM2:INotifyPropertyChanged
{
public Content VM2 { get{...} set{...} }
}
Now your data templates will describe wrapper class representations:
<DataTemplate DataType="{x:Type local:WrapperVM1}">
<TextBlock Text={Binding Content.SomPropertyInVM1}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:WrapperVM2}">
<TextBlock Text={Binding Content.SomPropertyInVM2}"/>
</DataTemplate>
As you can see if you substitute the Content property of the wrapper with a new instance of VM this won't recreate the view but all bindings will update. However if you need to switch to other type of VM you will have to substitute the Wrapper class by the appropriate Wrapper.

Categories

Resources