MVVM for tabcontrol based application - c#

In my wpf application the main view has 5 tabs with 5 different usercontrols , since the user controls are not related to each other, I have created 5 different view models (apart from the main viewmodel).
I thought of having a List or dictionary to have the list of usercontrols and its viewmodels,
Now, I would like to bind the tabitems with the list of usercontrols and assign the datacontexts, but since the list or dictionary can be changed, I dont find a way to bind the usercontrols to the tabitems.
For example, If I have a single tab which will be associated with a usercontrol I can assign
tab1View tview=new tab1View();
tview.DataContext= new tab1ViewModel();
tab1.Content=tview;
But how can I do the same from a list which has the reference of the view and viewmodels of the usercontrols?
Please teach me a best way to achieve this.
**Answer: **
I got the answer for what I need.
First, Generic type collection of the view models should be created
C# - Multiple generic types in one list
public abstract class Metadata
{
}
public class Metadata<DataType> : MetaData where DataType : class
{
private DataType mDataType;
}
List<Metadata> metadataObjects;
metadataObjects.Add(new Metadata<tab1ViewModel>());
metadataObjects.Add(new Metadata<tab2ViewModel>());
Then create a DataTemplate selector if multiple views are to be be referenced with same viewmodel or just apply the DataTemplate

There are a few ways to handle this, though I'd look at using frameworks to help you with MVVM. I myself promote Prism.
View Injection
View Discovery
DataTemplates - Sample
With DataTemplates you're defining in XAML (or in code, but XAML is more likely) which view to "automagically" apply to a ContentControl based upon the view-model (DataContext).
Somewhere in the XAML resources:
<DataTemplate DataType="{x:Type ViewModel:GeneralSettingsViewModel}">
<View:GeneralSettingsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:AdvancedSettingsViewModel}">
<View:AdvancedSettingsView/>
</DataTemplate>
Somewhere in the XAML file that has the resources applied to it:
<TabControl ItemsSource="{Binding MyViewModelCollection}" />
Note: This only works if you have one view-model per DataTemplate in the scoped resource.
DataTemplateSelector
If you have a view-model that can be applied to multiple views and you determine those views through additional logic, you would want to use a DataTemplateSelector. Here is an example:
Somewhere in the XAML resources:
<!-- Possible collision because the DataType is of the same type -->
<DataTemplate x:Key="GeneralSettingsTemplate"
DataType="{x:Type ViewModel:SettingsViewModel}">
<View:GeneralSettingsView/>
</DataTemplate>
<DataTemplate x:Key="AdvancedSettingsTemplate"
DataType="{x:Type ViewModel:SettingsViewModel}">
<View:AdvancedSettingsView/>
</DataTemplate>
<local:SettingsDataTemplateSelector x:Key="SettingsTemplateSelector"
GeneralSettingsTemplate="{StaticResource GeneralSettingsTemplate}"
AdvancedSettingsTemplate="{StaticResource AdvancedSettingsTemplate}" />
Somewhere in the XAML file that has the resources applied to it:
<TabControl ItemsSource="{Binding MyViewModelCollection}"
ItemTemplateSelector="{StaticResource SettingsTemplateSelector}" />
SettingsTemplateSelector.cs:
public class SettingsDataTemplateSelector : DataTemplateSelector
{
public DataTemplate GeneralSettingsTemplate { get; set; }
public DataTemplate AdvancedSettingsTemplate { get; set; }
public override DataTemplate SelectTemplate(Object item,
DependencyObject container)
{
var vm = item as SettingsViewModel;
if (vm == null) return base.SelectTemplate(item, container);
if (vm.IsAdvanced)
{
return AdvancedSettingsTemplate;
}
return GeneralSettingsTemplate;
}
}
MSDN: Prism Navigation - http://msdn.microsoft.com/en-us/library/gg430861(v=PandP.40).aspx
This covers Prism Regions as well as other parts of navigation.
MSND: View Discovery vs View Injection - http://msdn.microsoft.com/en-us/library/ff921075(v=pandp.20).aspx
This section covers the differences of View Discovery and View Injection and when to use each.

Create a collection of your viewmodels that you bind to the ItemsSource of the tab control. Then create a DataTemplateSelector to select a view for each viewmodel.

Related

TabControl with two data bindings

I have a TabControl that I want to have 2 data bindings. One is to have a TabItem for each item in a list and another single TabItem that is a Summary and uses an ItemsControl to show a template of controls for each item in another list.
I can set a TabControl to either one of these two types of data binding but not both of them at the same time. How could I get a TabControl to do both?
Could I somehow add an extra TabItem to the TabControl that has ItemSource, Binding set? Then I could load the new extra TabItem with XAML.
Expose a Combined Collection
You can expose a collection in your view model that combines both the collection and the addtional item like below (if you do not modify the collection, you can also use any other enumerable type).
public class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
var myTabItemCollection = // ...get the items collection.
var mySummaryTabItem = // ...get the summary item.
TabItems = new ObservableCollection<string>(myTabItemCollection)
{
mySummaryTabItem
};
}
public ObservableCollection<MyTabItemViewModel> TabItems { get; }
// ...other properties, methods, ...
}
Using a Composite Collection
There is a dedicated CompositeCollection type for combining one or multiple collections and single items in XAML. However, this type has severe shortcommings in terms of data-binding. It does neither derive from FrameworkElement, nor is it a Freezable, which makes it very difficult, cumbersome and error-prone to work with. I will present one solution with this type for the sake of completeness. Let's assume this view model:
public class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<MyTabItemViewModel> TabItems { get; }
public MyTabItemViewModel Summary { get; }
// ...other properties, methods, ...
}
In theory this would be easy if CompositeCollection would be to data-bindable (this does not work):
<TabControl>
<TabControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding TabItems}" />
<TabItem DataContext="{Binding Summary}"
Header="{Binding}"
Content="{Binding}" />
</CompositeCollection>
</TabControl.ItemsSource>
</TabControl>
In order to make it work, your can apply various approaches like in these related questions:
How do you bind a CollectionContainer to a collection in a view model?
CompositeCollection + CollectionContainer: Bind CollectionContainer.Collection to property of ViewModel that is used as DataTemplates DataType
Binding to a single element inside a CompositeCollection
As an example, I will use a binding proxy type to enable binding inside CompositeCollection. It will bind to the DataContext of the TabControl and expose it with the Data property. Since the proxy is a resource, the CompositeCollection can refer to it as source for binding with StaticResource.
<TabControl x:Name="TabControl">
<TabControl.Resources>
<local:BindingProxy x:Key="TabControlBindingProxy" Data="{Binding}" />
</TabControl.Resources>
<TabControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Data.TabItems, Source={StaticResource TabControlBindingProxy}}" />
<TabItem DataContext="{Binding Data.Summary, Source={StaticResource TabControlBindingProxy}}"
Header="{Binding}"
Content="{Binding}" />
</CompositeCollection>
</TabControl.ItemsSource>
</TabControl>
This will also work for multiple collections and single items, but be aware that this and the other approaches from the related questions - although they compile and run just fine - might crash the Visual Studio designer, which is unfortunate. Consequently, I recommend you to take the approach of exposing a combined collection, which is more reliable, easier to comprehend and simple.

C# WPF x:Type Markup that includes classes that inherit a class

I am working one some custom templates for a TreeView using HierarchicalDataTemplate. However, I am having issues getting it to work with a broad range of classes that inherit from a root class. It appears that the X:Type is very specific and won't trigger on classes the inherit from the class given. Below is some further information to help describe it.
I have a root class called Event that has 50+ other classes that inherit it and extend of it.
When I use the following DataType="{x:Type events:Event} , it will only work if the object is just the base class.
I would prefer not have 50+ HierarchicalDataTemplates in my XAML file, so is there any method that would make it work?
It's not really the {x:Type } fault, it is just the way DataTemplate is resolved from resources. Consider using custom DataTemplateSelector. Quick example:
class ItemTemplateSelector : DataTemplateSelector
{
public DataTemplate EventTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if(item is Event)
{
return EventTemplate;
}
// TODO: templates for other types
return null;
}
}
Data template definition:
<FrameworkElement.Resources>
<local:ItemTemplateSelector x:Key="ItemTemplateSelector">
<local:ItemTemplateSelector.EventTemplate>
<!-- template for event -->
<HierarchicalDataTemplate>
<TextBlock Text="Event" />
</HierarchicalDataTemplate>
</local:ItemTemplateSelector.EventTemplate>
</local:ItemTemplateSelector>
</FrameworkElement.Resources>
Usage:
<TreeView ItemTemplateSelector="{StaticResource ItemTemplateSelector}">
</TreeView>

WPF Creating different ListBox row templates based on a bound value

I do not have any code at the moment to share. Just a design question.
I have a class that defines a label, and an associated entry type, which I would like to bind to a ListBox. If the type is for example, "Postal Code", I need the ListBox to create the row as a TextBlock and a TextBox. For "Yes/no", I need it to know instead to create a TextBlock with a CheckBox beside it. There will likely be 7 or 8 of these different row types.
What is the best way to approach this?
Have a look at the ItemTemplateSelector Property. This property allows you to provide custom logic for choosing which template to use for each item in a collection.
First define your various templates in a resource dictionary...
<Application>
<Application.Resources>
<DataTemplate x:Key="TextBoxTemplate">
<!-- template here -->
</DataTemplate>
<DataTemplate x:Key="CheckBoxTemplate">
<!-- template here -->
</DataTemplate>
</Application.Resources>
</Application>
Then, create a custom DataTemplateSelector...
public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var myObj= item as MyObject;
if (myObj != null)
{
if (myObj.MyType is PostalCode)
{
return Application.Resources["TextBoxTemplate"] as DataTemplate;
}
if (myObj.MyType is YesNo)
{
return Application.Resources["CheckBoxTemplate"] as DataTemplate;
}
}
return null;
}
}
Then, its just a matter of using the ItemTemplateSelector property...
<Window>
<Window.Resources>
<local:MyTemplateSelector x:Key="tempSelector" />
</Window.Resources>
<ListBox ItemSource="{Binding items}" ItemTemplateSelector="{StaticResource tempSelector}" />
</Window>
You can use the DataTrigger class.
A DataTrigger allows you to set property values when the property value of the data object matches a specified Value.
Alternatively, you can use the DataTemplateSelector class.
Typically, you create a DataTemplateSelector when you have more than one DataTemplate for the same type of objects and you want to supply your own logic to choose a DataTemplate to apply based on the properties of each data object.
The best way to approach this would be to have a collection property containing all of the items that you want to see in your ListBox, bind that collection to a control that displays lists of items, and use different data templates to change the visuals used for each type of item.
For example, you might have a postal code type:
public class PostalCodeEntry
{
public string Value { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
And a "Boolean" type:
public class BooleanEntry
{
public bool Value { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
You said you wanted a label for each entry type, so a base class would be a good idea:
public abstract class EntryBase
{
public string Label { get; set; } // Implement using standard INotifyPropertyChanged pattern
}
Then BooleanEntry and PostalCodeEntry would derive from EntryBase.
That's the Models sorted. You just need a collection of these so that you can bind to them from the UI. Add an appropriate collection property to your Window or ViewModel:
public ObservableCollection<EntryBase> Entries { get; private set; }
In your UI (the View, implemented in XAML), use an instance of a control that knows how to bind to a list of items and visualize them. In WPF this would be an ItemsControl or a control that derives from it such as ListBox or ListView:
<ItemsControl ItemsSource="{Binding Entries}" />
You can see how we bind the ItemsSource property to our code-behind property named Entries. The ItemsControl (and its descendants) knows how to convert those items into a visual representation. By default, your custom object (EntryBase in our example) will be converted into a string and displayed as a text block. However, by using data templates you can control how the conversion from object to visual happens.
If you add a couple of data templates to the resources section like so:
<Window ... xmlns:my="clr-namespace:---your namespace here---">
<Window.Resources>
<DataTemplate DataType="{x:Type my:PostalCodeEntry}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Label}" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type my:BooleanEntry}">
<CheckBox Content="{Binding Label}" IsChecked="{Binding Value}" />
</DataTemplate>
</Window.Resources>
Then add the <ItemsControl ... element after that, then you should see a TextBlock/TextBox combo for PostalCodeEntry types and a CheckBox for BooleanEntry types.
Hopefully if you can get this working it will give you an idea how you could extend it to cope with other model types and their matching data templates.

MVVM and dynamic generation of controls

i've written a tool that generates sql queries using GUI, i want to rewrite the tool using MVVM and WPF, every sql column type has a different control as you can see in the following image
i add a column filter control based on the sql column type, and i generate the controls using code, just like i used to do in windows forms.
in MVVM i've read that the view is writtien enteirly using XAML,
does MVVM suite such application where i have to add different user
controls dynamically to a stack panel?
The controls won't exist in the view unless some column is double clicked, that means the control won't be available in the xaml and won't be hidden or collapsed.
is there any way that i can avoid the bindings in the code behind?
should i create a user control for each column type?
in general what is the best approach to devlop such application with complex and dynamic ui using mvvm?
Guess I know how to achieve that, but it is very complex stuff. First you should comprehend MVVM basic concepts.
Main ViewModel should be a class with ObservableCollection of ViewModels, each of them represents a column with its data and properties.
interface IViewModel : INotifyPropertyChanged,IDisposable
{
}
interface IColumnViewModel : IViewModel
{
}
class ViewModelBase : IViewModel
{
// ... MVVM basics, PropertyChanged etc. ...
}
class MainViewModel : ViewModelBase
{
ObservableCollection<IColumnViewModel> Columns {get; set}
}
In View I suppose something like ItemsControl with ItemTemplate, that should embed ContentControl with DataTemplate, that shall be automatically selected by WPF according to binded DataContext of list item. StackPanel itself is not suitable for that, but it can be invoked as ItemsPanelTemplate
<Window
xmlns:v="clr-namespace:WpfApplication.Views"
xmlns:vm="clr-namespace:WpfApplication.ViewModels">
<Window.Resources>
<DataTemplate DataType="{x:Type TypeName=vm:TextColumnViewModel}">
<v:TextColumnView/>
</DataTemplate>
</Window.Resources>
<ItemsControl
ItemsSource="{Binding Columns}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
So, you should build View/ViewModel pair for every column type.
Hope, my example will help. Good luck with your girlfriend and MVVM :)
If I've understood your scenario correctly :
You can use Data Templates & Items Templates
For example I've written an application which loads Data into Collection and then shows each item of that collection in a Wrap Panel [ Or stack panel ] based on defined data template.
And Wrap penel items are in sync by the collection itself within two way binding
You should consider using Observable Collections to achieve this goal
Then you can fill the collection and see the results on a view
I hope this helps
To write something like this in MVVM, you would have one view that is say, your content area. That view would have a view model, one of the properties of that view model would be a view, or several properties of that view model would be a view. It takes a bit to wrap your head around at times, but if you use Inversion of Control and Dependency Injection properly a view of views is very manageable in an MVVM pattern.
Well, your view isn't written entirely in XAML - you generate controls in C#.
I don't think you'll gain something from rewriting this and fitting it into an MVVM mold. Just keep the code as it is now and enjoy.

How do I set view/view model data template at runtime?

This MVVM stuff is making my head hurt. I have an application which has a list of editors in a left pane. On the right is a tab control where the editors will be displayed. I have a main application view model that contains a collection of view models. I call this collection Workspaces. This is borrowed from the MvvmDemoApp that Microsoft provides here.
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
...
}
}
These workspaces are bound to a tab control in the main application window like so:
<DataTemplate x:Key "WorkspacesTemplate">
<TabControl
IsSynchonizedWithCurrentItem="True"
ItemSource="{Binding Workspaces}"
SelectedItem="{Binding ActiveWorkspace}"/>
</DataTemplate>
...
<ContentControl
Content="{Binding}"
ContentTemplate="{StaticResource WorkspacesTemplate}"/>
The view models are tied to a view using DataTemplates like so:
<DataTemplate DataType="{x:Type vm:MessageLogViewModel}">
<vw:MessageLogView/>
</DataTemplate>
This works fine. However, now I need to make the application configurable where the list of editors are read from a config file. I imagine this config file will contain the view and view model components for each editor. But, how do I tie the two together so that when someone binds to a view model (or a collection of view models), the correct view gets displayed (similar to what the DataTemplate does but in code, not XAML)?
I'm trying to stay away for Inversion of Control (IoC) techniques. I'm not sure our team is ready for that must sophistication.
IoC is the perfect solution for this however without this option you could creating the XAML data template in the view model using an XmlWriter and expose it as a property to bind to.
Edit: Bindings
You have your list of view models. Create and expose this XamlTemplate property in each view model (in a base view model class). The property should create Xaml along the lines of:
<DataTemplate xmlns:vw="...">
<vw:MessageLogView/>
</DataTemplate>
Then use a ContentControl to bind to:
<ContentControl Content="{Binding ViewModel}"
ContentTemplate="{Binding ViewModel.XamlTemplate}" />

Categories