Navigation with Frame and Combobox MVVM - c#

I'm trying to get my head around MVVM, i'm currently stuck on how to handle navigation.
Currently I have a page and within that page is a frame, that frame is responsible for framing in various other pages. Navigation was previously handled with a drop down box and on selection changed it would navigate that way.
I'm not sure how I could do this without touching the frame from the model view which would end up breaking mvvm.
In the end what I am trying to accomplish is, clicking on the combobox, selecting an item and then having then frame below navigate to the correct view.
I'm not using Prism or any other framework with MVVM, just trying to do it all manually.

The ComboBox would display an ObservableCollection of frame items exposed by your main viewmodel, and the viewmodel will have another property for the selected item.
Your main viewmodel and the frame item viewmodels all inherit from a ViewModelBase class which implements INotifyPropertyChanged, and maybe some other stuff.
So, C#:
public ObservableCollection<ViewModelBase> FrameItems { get; protected set; }
private ViewModelBase _selectedFrameItem;
public ViewModelBase SelectedFrameItem {
get { return _selectedFrameItem; }
set {
value = _selectedFrameItem;
// Defined in ViewModelBase
OnPropertyChanged();
}
}
Your main viewmodel will populate FrameItems in its constructor:
public MainViewModel()
{
FrameItems = new ObservableCollection<ViewModelbase> {
new IceCreamMenu(),
new SmurfOptions(),
new MagicSparklePonyFourierTransformConfiguration()
};
}
Every frame item is a subclass of ViewModelBase. It exposes properties with notifications, including ObservableCollections of any set of child things it may have. And we'll display it by writing a datatemplate for it in just a bit.
Let's assume that you've given your ViewModelBase class a String Title { get; set; } property. Or maybe you'll want to write a subclass of ViewModelBase that introduces Title; your call. For now let's put it in ViewModelBase for simplicity.
XAML -- this leaves out all the layout, but you don't need that here.
<ComboBox
ItemsSource="{Binding FrameItems}"
SelectedItem="{Binding SelectedFrameItem}"
DisplayMemberPath="Title"
/>
<Frame Content={Binding SelectedFrameItem}" />
OK, but how on earth does it know what to do with SelectedFrameItem?!
Easy! Write a resource dictionary called, say, ViewModelDataTemplates.xaml, and merge it into App.xaml so its contents are "visible" in any XAML in your application.
App.xaml
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Source is a relative path from project root directory -->
<ResourceDictionary Source="ViewModelDataTemplates.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
...plus whatever theme stuff or whatever.
In ViewModelDataTemplates.xaml, define data templates for your frame item classes.
Say you've got an IceCreamMenu viewmodel, with a collection of Flavors
public ObservableCollection<IceCreamFlavor> Flavors { get; protected set; }
...and a SelectedFlavor. You'd define the namespace vm appropriately with an xmlns:vm attribute on the resource dictionary.
ViewModelDataTemplates.xaml
<DataTemplate DataType="{x:Type vm:IceCreamMenu}">
<Grid>
<ListBox
ItemsSource="{Binding Flavors}"
SelectedItem="{Binding SelectedFlavor}"
/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:IceCreamFlavor}">
<StackPanel Orientation="Horizontal">
<Border
Height="20"
Width="20"
Margin="4"
Background={Binding Color, Converter={StaticResource ColorToBrushConverter}}"
/>
<Label Content="Name" />
</StackPanel>
</DataTemplate>
If you've got existing UserControls that you want to use via datatemplates, that's easy: Say you've got a NotesTabView UserControl that's a view for your NotesTabViewModel, you could define a DataTemplate like this:
<DataTemplate DataType="{x:Type vm:NotesTabViewModel}">
<vw:NotesTabView />
</DataTemplate>

#EdPlunkett: As an alternative to DataTemplate for each view, you can bind your frame to selected page viewmodel using ViewModelToViewConverter like I did here: https://stackoverflow.com/a/31721236/475727
Implicit DataTemplates and DataTemplateSelectors are unique to WPF and XAML, so people think it's recommended solution, but I think it's not suitable for navigation. It feels hackish and it smells with violation of DRY principle.

Related

User controls for creating certain models

I'm new to WPF and I found that creating a custom component for my case would be the best, so please tell me if I'm wrong at first. The purpose of this idea is to reuse it in other scenarios as needed.
The Model:
public class FooModel
{
public string Whatever { get; set; }
}
The ViewModel:
public class FooViewModel
{
public FooModel Foo { get; set; }
public ICommand CreateCommand { get; set; } = new AnotherCommandImplementation<FooModel>(model =>
{
// model is null! :(
});
}
The UserControl:
<UserControl>
<UserControl.DataContext>
<local:FooViewModel />
</UserControl.DataContext>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Foo.Whatever}" Height="23" Width="120"/>
<Button CommandParameter="{Binding Foo}" Command="{Binding CreateCommand}" Width="80" Content="Create"/>
</StackPanel>
</UserControl>
Why is Foo null and how do I fix it?
UPDATE
As requested, here's the current DataTemplate technique attempt:
App.xaml:
<Application>
<Application.Resources>
<DataTemplate DataType="{x:Type vms:KeyboardActionViewModel}">
<ctrs:KeyboardActionControl />
</DataTemplate>
</Application.Resources>
</Application>
Window:
<Window>
<Window.DataContext>
<vms:ActionExecutorViewModel />
</Window.DataContext>
<StackPanel>
<CheckBox IsChecked="{Binding Enabled}" Content="Enabled" />
<UserControl Content="{Binding Action}" />
</StackPanel>
</Window>
ViewModel:
public class ActionExecutorViewModel : ViewModel<ActionExecutor>
{
private Boolean enabled;
private ActionViewModel action;
public ActionExecutorViewModel()
{
Action = new KeyboardActionViewModel(); // Test
}
public ActionViewModel Action
{
get => action;
set => AssignAndRaiseEventOnPropertyChange(ref action, value);
}
public Boolean Enabled
{
get => enabled;
set => AssignAndRaiseEventOnPropertyChange(ref enabled, value);
}
public override ActionExecutor BuildModel()
{
var executor = new ActionExecutor();
executor.Action = action.BuildModel();
return executor;
}
}
KeyboardActionControl:
<UserControl>
<Label Background="Aqua">Asadsadsad</Label>
</UserControl>
ActionViewModel is an abstract class where KeyboardActionViewModel inherits from it.
As Sereja notes, your proximal issue is that Foo is null. You never created it, so it's not there. It should probably be instantiated by FooViewModel, but it's possible that the creator of FooViewModel should create Foo as well. Without knowing the semantics, I can't be sure. The view should absolutely not be responsible for creating either one.
But there are bad assumptions built into what you're doing. Let's correct those and get you on the right track.
ViewModelBase implements INotifyPropertyChanged. Examples abound. The view XAML snippets below are intended to be partial: There are bits of UI which are not illustrated, as they shouldn't present any difficulties.
public class MainViewModel : ViewModelBase
{
public ActionExecutorCollectionViewModel ActionExecutors { /* INPC stuff */ }
// ViewModels create their own children.
= new ActionExecutorCollectionViewModel();
}
public class ActionExecutorCollectionViewModel : ViewModelBase
{
public ObservableCollection<ActionExecutor> Items { /* INPC stuff */ }
public ActionExecutor NewActionExecutor { /* INPC stuff */ }
// Create new ActionExecutor and assign to NewActionExecutor
public ICommand CreateActionExecutor { /* ... */ }
// Add NewActionExecutor to Items and set NewActionExecutor to null
public ICommand SaveActionExecutor { /* ... */ }
}
Write an implicit DataTemplate for each of the above. In MainViewModel's DataTemplate, there's something like this:
<ContentControl Content="{Binding ActionExecutors}" />
That displays the ActionExecutorsViewModel with its implicit DataTemplate, which contains something like this, among other things:
<Button
Command="{Binding CreateActionExecutor}"
Content="Create"
/>
<Button
Command="{Binding SaveActionExecutor}"
Content="Save"
/>
<ContentControl
Content="{Binding NewActionExecutor}"
/>
ActionExecutor needs some kind of crude class factory to create its own Action. You have two action types now. I would advise against going crazy at the moment trying to write a perfect architecture for adding new ones in the future. I would instead suggest giving ActionExecutor a public readonly collection of action type options, probably values from an enum: public ActionType { Mouse, Keyboard }, and a public ActionType ActionType property. When ActionType changes, create a new action of the new type and assign it to the Action property. ActionType’s setter should call a protected method which does that. There are other, more clever options for this, but the above design is reasonably maintainable and has served well in thousands of production applications.
In ActionExecutor's implicit DataTemplate, you would have a combobox which lets the user select a type of action from the ActionTypes collection. Its SelectedItem property is bound to ActionType. This is how actions are created.
ActionExecutor's DataTemplate contains something like this:
<CheckBox Content="Enabled" IsChecked="{Binding Enabled}" />
<ComboBox ItemsSource="{Binding ActionTypes}" SelectedItem="{Binding ActionType}" />
<ContentControl Content="{Binding Action}" />
All viewmodels below MainViewModel are created by their immediate parent viewmodels, never never never ever by a view. Think of the viewmodel "tree" as the skeleton or framework of the application. Views just display bits of it as needed. Viewmodels need to communicate with each other; views don’t. They just reflect and instigate state changes in their viewmodels. The window can create its viewmodel in its constructor, or in XAML as <Window.DataContext><local:MainViewModel /></Window.DataContext>. Either is fine, but doing it in the constructor allows you to call a constructor that has parameters.
Thus, with that one exception, a UserControl always gets its DataContext from context, never by creating it. This is a practical matter, not ideological: It makes writing and maintaining the application very much easier than the alternative. Many vexing issues are swept out of existence when you follow this rule. It's rare for a UserControl in a well-designed WPF application to define dependency properties. The purpose of a UserControl is to display a viewmodel. Other types of controls will define vast, lavish, glittering assortments of dependency properties. Not UserControls.
You can write UserControls and put them in DataTemplates, or just write DataTemplates. I believe that writing UserControls is a good idea. A DataTemplate containing a UserControl looks EXACTLY LIKE THIS:
<DataTemplate DataType="{x:Type ActionExecutor}">
<local:ActionExecutorUserControl />
</DataTemplate>
DataContext="{Binding SomeProperty}" is essentially always wrong. It’s a “code smell” which indicates that somebody doesn’t understand XAML very well yet.
If some part of the above doesn't make sense to you, I'll be happy to help you fill that gap in your knowledge. If you believe that some part of it conflicts with your requirements, you may very well be mistaken. However, it is your responsibility to fully understand and codify your own requirements, and to communicate those requirements clearly.
UPDATE
Implicit DataTemplates
An implicit datatemplate is 1) a datatemplate defined as a resource in an accessible ResourceDictionary, with 2) a DataType attribute specifying which one of your classes you want to display with it.
App.xaml
<Application.Resources>
<DataTemplate DataType="{x:Type ActionExecutorCollectionViewModel}">
<local:ActionExecutorCollectionUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type ActionExecutor}">
<local:ActionExecutorUserControl />
</DataTemplate>
<DataTemplate DataType="{x:Type MouseAction}">
<local:MouseActionUserControl />
</DataTemplate>
<!-- And so on and so forth. -->
</Application.Resources>
MainWindow.xaml
MainWindow's DataContext is your MainViewModel, which I've partially defined above.
<Grid>
<!--
MainViewModel.ActionExecutors is of type ActionExecutorCollectionViewModel.
If you defined an implicit datatemplate for that class in some ResourceDictionary
that's in scope here (e.g., App.xaml), this UserControl will automatically
use that datatemplate.
-->
<UserControl Content="{Binding ActionExecutors}" />
</Grid>
ActionExecutorUserControl.xaml
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label>Interval</Label>
<TextBox Text="{Binding Interval}" />
</StackPanel>
<CheckBox IsChecked="{Binding Enabled}">Enabled</CheckBox>
<!--
If you have implicit datatemplates defined for all your action types,
the framework will automatically give this UserControl the correct template
for whatever actual type of action the Action property refers to.
This is where we begin to see the real value of implicit datatemplates.
-->
<UserControl Content="{Binding Action}" />
</StackPanel>
There is not constructor that initializes Foo with non-default values (which null is for reference types). That's the reason. At least, provide such a constructor, or - more WPFic way - create a DataContext="{Binding Foo}"; that's probably what you wanted, however your XAML is wrong then: you are creating new instance all the time rather than consuming the view model's Foo instance.
P.S. More than that, for UserControls it is command to expose a DependencyProperty to take the underlying model; so it would look like <UserControl Model="{Binding Foo}" ... />.

Wpf call function of ContentControl view

I am pretty new to C# so I am just starting to learn the basics. Right now I have a ContentControl inside a Window like this:
<ContentControl Content="{Binding}" x:Name="SubView"/>
And I configured my resources of the Windows like this:
<Window.Resources>
<DataTemplate x:Name="StammdatenViewTemplate" DataType="{x:Type viewmodels:StammdatenViewModel}">
<views:StammdatenView DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="AdministrationViewTemplate" DataType="{x:Type viewmodels:AdministrationViewModel}">
<views:AdministrationView DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>
In my Window class I am setting the DataContext like this:
DataContext = new StammdatenViewModel();
Here is the thing I would like to do. I want to disable all the TextBoxes inside the ContentControl. I thought about adding a function to my StammdatenView.xaml.cs class (which is the class of my subview), then firing the event from the Window somehow. Though I would need access to the function inside the subview. Is that somehow possible and if yes how? Or would anyone suggest a different approach?
Thanks in advance.
Or would anyone suggest a different approach?
Yes. You should bind the IsEnabled property of each TextBox in the StammdatenView to a boolean property of the StammdatenViewModel.
You can then disable the TextBoxes by setting the source property in the view model class. This is one of the key aspects of the MVVM design pattern, i.e. that you handle your application logic in the view model.
Make sure that the view model class implements the INotifyPropertyChanged interface and provide change notifications as explained on MSDN.
You can create a INotifyPropertyChanged event on your view model, then bind it to the 'IsEnabled={Binding IsTextBoxEnabled}' attribute in your view template for the textbox.
public class ViewModel : BaseViewModel
{
private bool _isTextBoxEnabled;
public bool IsTextBoxEnabled
{
get { return _isTextBoxEnabled; }
set
{
if (value != _isTextBoxEnabled)
_isTextBoxEnabled = value;
this.RaisePropertyChanged("IsTextBoxEnabled");
}
}
}
XAML
<DataTemplate x:Key="template">
<StackPanel Orientation="Horizontal" DataContext="{Binding}">
<TextBox IsEnabled="{Binding IsTextBoxEnabled}" />
</StackPanel>
</DataTemplate>

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.

How to add controls in the tab item programmatically in wpf with MVVM

I have created a tab control and Created the tabItems dynamically, but i dont know how to add controls into the tabItems using MVVM. Could any one help me
There are a few ways to programmatically add Tab Items in WPF and I am going to show you a simple example on how I deal with this in my application.
First I host a collection of the ViewModels for the TabItems (or Workspaces as I refer to them) in my MainWindowViewModel.cs:
private ObservableCollection<WorkspaceViewModel> _workspaces;
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
}
return _workspaces;
}
}
Next I add a reference to the various controls in my MainWindow.xaml. This is important as we want to make sure that whenever the collection contains a ViewModel that it displays the appropriate View for that Model.
<Window.Resources>
<DataTemplate DataType="{x:Type vm:MyUserControlViewModel}">
<vw:MyUserControlView/>
</DataTemplate>
</Window.Resources>
If you have multiple types of UserControls you simply add them all here like this:
<Window.Resources>
<DataTemplate DataType="{x:Type vm:FirstUserControlViewModel}">
<vw:FirstUserControlView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SecondUserControlViewModel}">
<vw:SecondUserControlView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ThirdUserControlViewModel}">
<vw:ThirdUserControlView/>
</DataTemplate>
</Window.Resources>
Next we add the TabControl and bind it to our Workspace Collection.
<TabControl ItemsSource="{Binding Workspaces}"/>
Then I simply add my ViewModels to the Collection to have them show up in the TabControl.
Workspaces.Add(new FirstUserControlViewModel());
Workspaces.Add(new SecondUserControlViewModel());
Workspaces.Add(new ThirdUserControlViewModel());
My WorkspaceViewModel that I base the TabItem collection of is very simple and looks something like this:
public abstract class WorkspaceViewModel : BaseViewModel
{
public String HeaderText { get; set; }
public override string ToString()
{
return HeaderText;
}
}
Adding a TabItem:
To create a TabItem you simply create a UserControl and ViewModel like you normally would using WPF and the MVVM pattern.
namespace MyApplication.ViewModel
{
public class FirstUserControlViewModel : WorkspaceViewModel
{
public FirstUserControlViewModel ()
{
base.HeaderText = "My First Tab";
}
}
}
Next you need to bind a View to your new ViewModel.
<DataTemplate DataType="{x:Type vm:FirstUserControlViewModel }">
<vw:FirstUserControlView/>
</DataTemplate>
Then you create an instance of the ViewModel and add it to the collection in your MainWindowViewModel.
FirstUserControlViewModel firstvm = new FirstUserControlViewModel();
Workspaces.Add(firstvm);
And now the TabItem should show up in your TabControl.
Loading TabItems dynamically using Extensions:
In some cases you might even need to load TabItems from plugins dynamically without the host application first knowing about the TabItem. In these cases you need to have the plugin register the View and ViewModel with the application domain.
This is very easy to do, and actually something I do for one of my MEF based projects. I have an post here, with some additional details as well.
All you need to do is add a Resource Dictionary to your plugin/extension and make sure that the host application loads it once the plugin has been imported.
To show you a fast example I would have a View.xaml in my extensions:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:MyExtension.Test">
<DataTemplate DataType="{x:Type vw:TestViewModel}">
<vw:TestView/>
</DataTemplate>
</ResourceDictionary>
I then expose the ResourceDictinary using MEF to the Host like this:
private ResourceDictionary _viewDictionary = new ResourceDictionary();
public ResourceDictionary Dict
{
get
{
return _viewDictionary;
}
}
_viewDictionary.Source =
new Uri("/MyExtension.Test;component/View.xaml",
UriKind.RelativeOrAbsolute);
Last you use Application.Current.Resources.MergedDictionaries.Add to load the View.xaml into the host.
You Dont have to add controls you just have to specify the UserControl.
TabControl has two properties ItemTemplate && Content Template
ItemTemplate is for how the Tab will look where as
ContentTemplate is how the Tab Content will Look... so...
Xaml for the above
<TabControl Grid.Row="1"
ItemsSource="{Binding Path=TabList}"
SelectedItem="{Binding Path=SelectedTab,
Mode=TwoWay}"
<!--This is How tab will look--> >
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="20"
Height="20"
Margin="0,0,2,0"
Source="Images\TreeView\yourFavImg.png" />
<TextBlock Margin="0,4,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding Path=TabText}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<!--This will be the content for the tab control-->
<TabControl.ContentTemplate>
<DataTemplate>
<!--This User Control will contain the controls you like-->
<ViewLayer:YourFavUserControl />
</DataTemplate>
</TabControl.ContentTemplate>
you dont have to add controls if you use mvvm. you just have to create datatemplates for your viewmodel objects you wanna display.
all you need is a contentcontrol/presenter which is bind to your viewmodel and the datatemplate will show what you want.

UI design using MVVM pattern

I'm trying to choose the best way to implement this UI in MVVM manner. I'm new to WPF (like 2 month's) but I have huge WinForms experience.
The ListBox here act's like a TabControl (so it switches the view to the right), and contains basically the Type of item's displayed in tables. All UI is dynamic (ListBox items, TabItems and Columns are determined during run-time). The application is targeting WPF and Silverlight.
Classes we need for ViewModel:
public abstract class ViewModel : INotifyPropertyChanged {}
public abstract class ContainerViewModel : ViewModel
{
public IList<ViewModel> Workspaces {get;set;}
public ViewModel ActiveWorkspace {get;set;}
}
public class ListViewModel<TItem> where TItem : class
{
public IList<TItem> ItemList { get; set; }
public TItem ActiveItem { get; set; }
public IList<TItem> SelectedItems { get; set; }
}
public class TableViewModel<TItem> : ListViewModel<TItem> where TItem : class
{
public Ilist<ColumnDescription> ColumnList { get; set; }
}
Now the question is how to wire this to View.
There are 2 base approaches I can see here:
With XAML: due to lack of Generics support in XAML, I will lose strong typing.
Without XAML: I can reuse same ListView<T> : UserControl.
Next, how to wire data, I see 3 methods here (with XAML or without doesn't matter here). As there is no simple DataBinding to DataGrid's Columns or TabControl's TabItems the methods I see, are:
Use DataBinding with IValueConverter: I think this will not work with WPF|Silverlight out of the box control's, as some properties I need are read-only or unbindable in duplex way. (I'm not sure about this, but I feel like it will not work).
Use manual logic by subscribing to INotifyPropertyChanged in View: ViewModel.PropertyChanged+= ....ViewModel.ColumnList.CollectionChanged+= ....
Use custom controll's that support this binding: Code by myself or find 3d party controls that support this binding's (I don't like this option, my WPF skill is too low to code this myself, and I doubt I will find free controls)
Update: 28.02.2011
Things get worser and worser, I decided to use TreeView instead of ListBox, and it was a nightmare. As you probably guess TreeView.SelectedItems is a readonly property so no data binding for it. Ummm all right, let's do it the old way and subscribe to event's to sync view with viewmodel. At this point a suddenly discovered that DisplayMemberPath does nothing for TreeView (ummmm all right let's make it old way ToString()). Then in View's method I try to sync ViewModel.SelectedItem with TreeView's:
private void UpdateTreeViewSelectedItem()
{
//uiCategorySelector.SelectedItem = ReadOnly....
//((TreeViewItem) uiCategorySelector.Items[uiCategorySelector.Items.IndexOf(Model.ActiveCategory)]).IsSelected = true;
// Will not work Items's are not TreeViewItem but Category object......
//((TreeViewItem) uiCategorySelector.ItemContainerGenerator.ContainerFromItem(Model.ActiveCategory)).IsSelected = true;
//Doesn't work too.... NULL // Changind DataContext=Model and Model = new MainViewModel line order doesn't matter.
//Allright.. figure this out later...
}
And none of methods I was able to think of worked....
And here is the link to my sample project demonstrating Control Library Hell with MVVM: http://cid-b73623db14413608.office.live.com/self.aspx/.Public/MVVMDemo.zip
Maciek's answer is actually even more complicated than it needs to be. You don't need template selectors at all. To create a heterogeneous tab control:
Create a view model class for each type of view that you want to appear as tab items. Make sure each class implements a Text property that contains the text that you want to appear in the tab for its item.
Create a DataTemplate for each view model class, with DataType set to the class's type, and put the template in the resource dictionary.
Populate a collection with instances of your view models.
Create a TabControl and bind its ItemsSource to this collection, and add an ItemTemplate that displays the Text property for each item.
Here's an example that doesn't use view models (and that doesn't implement a Text property either, because the objects I'm binding to are simple CLR types), but shows how template selection works in this context:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:coll="clr-namespace:System.Collections;assembly=mscorlib">
<DockPanel>
<DockPanel.Resources>
<coll:ArrayList x:Key="Data">
<sys:String>This is a string.</sys:String>
<sys:Int32>12345</sys:Int32>
<sys:Decimal>23456.78</sys:Decimal>
</coll:ArrayList>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Int32}">
<StackPanel Orientation="Horizontal">
<TextBlock>This is an Int32:</TextBlock>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Decimal}">
<StackPanel Orientation="Horizontal">
<TextBlock>This is a Decimal: </TextBlock>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</DockPanel.Resources>
<TabControl ItemsSource="{StaticResource Data}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</DockPanel>
</Page>
Of course in a real MVVM application those DataTemplates would use UserControls to map each type to its view:
<DataTemplate DataType="{x:Type my:ViewModel}">
<my:View DataContext="{Binding}"/>
</DataTemplate>
Maciek and Robert already gave you some ideas on how to implement this.
For the specifics of binding the columns of the DataGrid I strongly recommend Meleak's answer to that question.
Similar to that you can use attached properties (or Behaviors) and still maintain a clean ViewModel in MVVM.
I know the learning curve for WPF is quite steep and you're struggling already. I also know that the following suggestion doesn't help that and even makes that curve steeper. But your scenario is complex enough that I'd recommend to use PRISM.
I wrote an article and a sample application with source code available, where I discuss and show the problems I have mentioned here and how to solve them.
http://alexburtsev.wordpress.com/2011/03/05/mvvm-pattern-in-silverlight-and-wpf/
In order to connect your ViewModel to your View you need to assign the View's DataContext. This is normally done in the View's Constructor.
public View()
{
DataContext = new ViewModel();
}
If you'd like to see your view model's effect at design time, you need to declare it in XAML, in the View's resources, assign a key to it, and then set the target's DataContext via a StaticResource.
<UserControl
xmlns:vm="clr-namespace:MyViewModels
>
<UserControl.Resources>
<vm:MyViewModel x:Key="MyVM"/>
</UserControl.Resources>
<MyControl DataContext={StaticResource MyVM}/>
</UserControl>
(The above is to demonstrate the design-time trick works)
Since you're dealing with a scenario that includes a container such as the TabControl I'd advocate considering the following things :
Hold your child ViewModels in a Property of type ObservableCollection
Bind the TabControls ItemsSource to that property
Create a new View that derives from TabItem
Use a template selector to automatically pick the type of the view based on the type of the view model.
Add IDisposable to yoour child ViewModels and create functionality to close the views.
Hope that helps a bit, if you have further questions let me know.

Categories

Resources