UI design using MVVM pattern - c#

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.

Related

How to properly remove Items from a ListView when the ItemTemplate is a User Control?

I tried to follow the example here:
WPF ListBox with self-removing items
It made sense but my issue was, the ListView itself is determining the template used. So it can easily customise the bindings to point to the correct target. I am however using MVVM and am struggling to fit the two together.
Example, if the template was:
<ListBox.ItemTemplate>
<DataTemplate>
<local:MyItemView/>
</DataTemplate>
</ListBox.ItemTemplate>
This suddenly becomes more difficult, as ideally, I want to reuse that view without hard coding the bindings.
I tried to use DependencyProperty to pass the List and the Element through, so I could delete it via command.
<ListBox.ItemTemplate Name="myList">
<DataTemplate>
<local:MyItemView TheList={Binding ElementName=myList, Path=DataContext.List} TheElement={Binding}/>
</DataTemplate>
</ListBox.ItemTemplate>
However, I had binding errors telling me that it couldn't convert the value for TheElement from MyClassViewModel to MyClass. Even if I commented that out TheList was always NULL.
Essentially I want:
class MyDataClass { // pretend there's more here}
class MyDataClassContainer
{
public ObservableCollection<MyDataClass> Items;
public void Add(MyDataClass);
public void Remove(MyDataClass);
}
class MyDataClassEntryViewModel
{
public static readonly DependencyProperty ListItemProperty = DependencyProperty.Register("TheClass", typeof(MyDataClass), typeof(MyDataClassEntryViewModel));
public static readonly DependencyProperty ListContainerProperty = DependencyProperty.Register("TheContainer", typeof(MyDataClassContainer), typeof(MyDataClassEntryViewModel));
public MyDataClass TheClass;
public MyDataClassContainer TheContainer;
public ICommand Delete = new DelegateCommand(RemoveItem);
private function RemoveItem(object parameter)
{
TheContainer.Remove(TheClass);
}
}
With the following templates:
MyDataClassEntryView.xaml
<UserControl>
<Grid>
<Button Content="Delete" Command="{Binding Path=Delete}"/>
</Grid>
</UserControl>
MyDataContainerView.xaml
<UserControl>
<ListView x:Name="listView" ItemsSource="{Binding Path=Container.Items}">
<ListView.ItemTemplate>
<DataTemplate>
<local:MyDataClassEntryView TheClass="{Binding}" TheContainer="{Binding ElementName=listView, Path=DataContext.Container}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>
Note: I have omitted most of the superfluous lines, as I'm trying to get a generic answer I can use everywhere. Not a hard coded single solution. I was basically want to keep the MVVM structure strong, without lots of hard coded and wiring in the background. I want to use the XAML as much as possible.
All the other methods I see to do with removing from a list, require all sorts of assumptions, such as using the SelectedIndex/Item, or using a method on the ContainerView itself to take the element as a parameter, cast it, then remove, etc. In short, most solutions are far too hard coded to the given examples. It feels like there should be an easy way to achieve this in WPF.
As the ListView issautomatically creating instances of my sub-ViewModel/Views, it's impossible for me to get any data in apparently. I just want to pass parameters along using bindings, basically.
Your button should look like this:
<Button Content="Delete"
Command="{Binding Path=Delete}"
CommandParameter="{Binding}/>
Then the remove command should look something like this:
private function RemoveItem(object parameter)
{
var item = parameter as MyDataClass
if(item != null)
TheContainer.Remove(item);
}
You do not need to pass the list to the UserControl within the ItemTemplate, since it doesn't need to know about the list at all
Edit:
I read over your question a few times to see what you were confused about so I will try to clarify.
Whether the ListView sets its own template in the Xaml, or you use another UserControl, the datacontext still gets passed down to the item. Regardless of how you decide to template the items, the ItemTemplate will have the datacontext of a single item from the ListView's items list.
I think your confusion comes in with having controls outside being brought in for templating. Think of it as if the Xaml from the control you brought in being cut and pasted into the DataTemplate of the ListView when running the program, and then it is really no different from being hard coded in there.
You cannot reach outside of a DataTemplate with Element bindings like you have tried.
Instead you need to use a relativesource like this.
<local:MyItemView TheList="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}, Path=DataContext.List}" />

"Dynamically" Generate a WPF View C# for Plotting with DynamicDataDisplay

I am writing a WPF application in C#. This application is design using MVVM.
Currently, I have a parent window with a few check boxes. Use user can check whichever boxes they want and then click the "plot" Button. Once they click "plot", a new child window comes up displaying the data on a single graph.
So, if I have only 1 check box checked, and then click "plot", I will see a graph with a single line on it. If I have 2 check boxes check and click "plot", I will see the same single graph, but it will have 2 lines on it.
My current Implementation:
Currently, I have a "view" class called GraphWindowView. The view obviously needs to know of which data to show. So to do that, I have dependency properties GraphWindowView.Dates and GraphWindowView.Data which ultimatley produces a graph of Data (y axis) vs. Dates (x axis).
Question: This current implementation of GraphWindowView is obviously restricted to only being able to graph one set of data (i.e. Data vs. Dates). I would like to make this (a lot) more extensible and have an arbitrary number of plots available depending on how much check boxes are checked. How would I go about doing this? I think I need to rethink my use of dependency properties...
>>> UPDATE
So I made a GraphLine class which should represent a line on the graph. The "graph" is actually a ChartPlotter element in the GraphWindowPresenter.xaml class. Additionally, I specified a DataType for the GraphLine objects, but that is all I understand. What are the next steps to this, how do I actually add the data to the graph? And how/where do I make instances of GraphLine to populate the ChartPlotter element? Sorry I am pretty lost on this, even after reading quite a few tutorials. Thanks for all the help so far, I really appreciate it!
GraphWindowView.xaml
<Window x:Class="BMSVM_Simulator.View.GraphWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:BMSVM_Simulator.ViewModel"
xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
x:Name="ThisGraphWindowInstance"
Title="Plot" Height="500" Width="750"
Icon="../res/qualcomm_q_icon.ico.ico"
MinWidth="400" MinHeight="300">
<Window.DataContext>
<ViewModel:GraphWindowPresenter/>
</Window.DataContext>
<d3:ChartPlotter Name="plotter" Margin="10,10,20,10">
<d3:ChartPlotter.HorizontalAxis>
<d3:HorizontalIntegerAxis Name="dateAxis"/>
</d3:ChartPlotter.HorizontalAxis>
<d3:ChartPlotter.VerticalAxis>
<d3:VerticalIntegerAxis Name="countAxis"/>
</d3:ChartPlotter.VerticalAxis>
<d3:Header FontFamily="Arial" Content="{Binding ElementName=ThisGraphWindowInstance, Path=title}"/>
<d3:VerticalAxisTitle FontFamily="Arial" Content="{Binding ElementName=ThisGraphWindowInstance, Path=yAxis}"/>
<d3:HorizontalAxisTitle FontFamily="Arial" Content="{Binding ElementName=ThisGraphWindowInstance, Path=xAxis}"/>
</d3:ChartPlotter>
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:GraphLine}">
<!--WHAT GOES HERE-->
</DataTemplate>
</Window.Resources>
</Window>
GraphLine.cs
namespace BMSVM_Simulator.ViewModel
{
class GraphLine
{
public string xAxis { get; private set; }
public string yAxis { get; private set; }
public string title { get; private set; }
public string legend { get; private set; }
public EnumerableDataSource<int> data { get; private set; }
public EnumerableDataSource<int> dates { get; private set; }
}
}
Most of these types of problems in WPF can be sorted out by some careful use of data binding and DataTemplates, rather than miles of procedural code. The general idea is that you create a custom class with all of the properties that are required to draw all of your lines. You would then declare a DataTemplate to define how the various properties are to be data bound, perhaps a little something like this:
<DataTemplate DataType="{x:Type YourXamlNamespacePrefix:GraphLine}">
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}" />
</DataTemplate>
Then you create a collection of your custom class instances and data bind it to some collection control, like an ItemsControl and each one will be automatically rendered in the correct location:
<ItemsControl ItemsSource="{Binding YourGraphLineCollection, RelativeSource={
RelativeSource AncestorType={x:Type YourXamlNamespacePrefix:YourControlName}}}" />
Welcome to the powerful world of WPF data binding and DataTemplates.
UPDATE >>>
The custom class to data bind to the Line elements is not a view model. Think of it as a data type class, for which you will declare a DataTemplate like the one above. When I said that it should have all of the required properties, if you look at the above example, you'll see that it would at least need four double properties to data bind to the four used properties of the Line element. However, you might also choose to add further properties to data bind to the Stroke, StrokeThickness or Fill properties for example.
As for where you should define the DataTemplate, it should be within scope of the items that have it applied. If you want to use it in one view, then put it in the UserControl.Resources section of that view. However, if you want to use the same DataTemplate, then you should put it into the Application.Resources section of the App.xaml file because those Resources are available application wide.
FINAL UPDATE >>>
As noted in my comment, teaching users how to use WPF is definitely out of scope for this website, so I won't be doing that. To learn about DataTemplates, you should read the Data Templating Overview page on MSDN. When you don't know about something, MSDN should always be your first place to search for answers.
I can give you a few last tips before I go: The DependencyProperty in your control should be of type ObservableCollection<GraphLine>. Inside your control, you should data bind them to some sort of ItemsControl as shown above - I changed the Binding Path in it because you should really use a RelativeSource Binding to locate the property in your situation (where YourControlName is the name of your UserControl where you want to draw the Line objects).
Finally, in your view model (that is linked with the view that contains your new UserControl that draws the lines), you'll need a collection property to data bind with the collection in the UserControl, let's say named YourGraphLineCollectionInViewModel:
<YourXamlNamespacePrefix:YourControlName YourGraphLineCollection="{Binding
YourGraphLineCollectionInViewModel}" />
It's in this view model that you add the instances of your GraphLine class into the YourGraphLineCollectionInViewModel collection and as long as you have set up your Binding Paths as shown here, they'll appear in your UI within the ItemsControl. I am assuming that you know how to correctly set your DataContext - if not, you can easily find out how to do that online.

Getting parent of new tab after adding to bound TabControl (mvvm)

I'm adding a close button to my tabs using the following guide:
http://www.codeproject.com/Articles/84213/How-to-add-a-Close-button-to-a-WPF-TabItem
This has become a problem because the event uses the 'parent' of the added tab to remove that tab from the tabcontrol. I'm binding the tab control using mvvm, so the parent property is apparently not being set and giving me a null reference exception for the parent when the event tries to remove from it.
Here's the binding so you get the idea:
<TabControl Name="tabControl" Margin="0,22,0.2,-5.2" ItemsSource="{Binding Tabs}" Background="#FF4C76B2"/>
Heres where the tabs are being added.
private void AddTab(object tabName)
{
ClosableTab newTab = new ClosableTab();
newTab.Title = "title?";
//newTab.Header = tabName;
TextBox test = new TextBox();
test.Text = "CONTENT (" + tabName + ") GOES HERE";
newTab.Content = test;
Tabs.Add(newTab);
OnPropertyChanged("Tabs");
}
Here is the event where the null reference is taking place:
void button_close_Click(object sender, RoutedEventArgs e)
{
((TabControl)this.Parent).Items.Remove(this);
}
As I see it there are two options:
try to find another way to remove the tab (without the parent
property)
try to find a way to somehow set the parent property (which cant be
done directly, it throws a compiler error)
That doesn't sound like MVVM to me. We work with data, not UI elements. We work with collections of classes that contain all of the properties required to fulfil some requirement and data bind those properties to the UI controls in DataTemplates. In this way, we add UI controls by adding data items into these collections and let the wonderful WPF templating system take care of the UI.
For example, you have a TabControl that we want to add or remove TabItems from... in a proper MVVM way. First, we need a collection of items that can represent each TabItem:
public static DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<string>), typeof(TestView));
public ObservableCollection<string> Items
{
get { return (ObservableCollection<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
I'm just using a DependencyProperty because I knocked this up in a UserControl and I'm just using a collection of strings for simplicity. You'll need to create a class that contains all of the data required for the whole TabItem content. Next, let's see the TabControl:
<TabControl ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ItemTemplate}" />
We data bind the collection to the TabControl.ItemsSource property and we set the TabControl.ItemTemplate to a Resource named ItemTemplate. Let's see that now:
xmlns:System="clr-namespace:System;assembly=mscorlib"
...
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type System:String}">
<TabItem Header="{Binding}" />
</DataTemplate>
This DataTemplate defines what each item in our collection will look like. For simplicity's sake, our strings are just data bound to the TabItem.Header property. This means that for each item we add into the collection, we'll now get a new TabItem with its Header property set to the value of the string:
Items.Add("Tab 1");
Items.Add("Tab 2");
Items.Add("Tab 3");
Note that I included the System XML Namespace Prefix for completeness, but you won't need that because your DataType will be your own custom class. You'll need more DataTemplates too. For example, if your custom class had a Header property and a Content property, which was another custom class, let's say called Content, that contained all of the properties for the TabItem.Content property, you could do this:
<DataTemplate x:Key="ItemTemplate" DataType="{x:Type YourPrefix:YourClass}">
<TabItem Header="{Binding Header}" Content="{Binding Content}" />
</DataTemplate>
<DataTemplate DataType="{x:Type YourPrefix:Content}">
<YourPrefix:SomeUserControl DataContext="{Binding}" />
</DataTemplate>
So this would give you TabItems with Headers set and Content that comes from SomeUserControl which you could design. You don't need to use UserControls, you could just add more UI controls to either DataTemplate. But you will need to add more controls somewhere... and more classes and properties, always remembering to correctly implement the essential INotifyPropertyChanged interface.
And finally, to answer your question in the proper MVVM way... to remove a TabItem, you simply remove the item that relates to that TabItem from the collection. Simple... or it would have been if you really had been using MVVM like you claim. It's really worth learning MVVM properly as you'll soon see the benefits. I'll leave you to find your own tutorials as there are many to chose from.
UPDATE >>>
Your event handling is still not so MVVM... you don't need to pass a reference of any view model anywhere. The MVVM way is to use commands in the view model. In particular, you should investigate the RelayCommand. I have my own version, but these commands enable us to perform actions from data bound Buttons and other UI controls using methods or inline delegates in the view model (where action and canExecute in this example are the CommandParameter values):
<Button Content="Close Tab" Command="{Binding CloseTabCommand}"
CommandParameter="{Binding}" />
...
public ICommand CloseTabCommand
{
get { return new ActionCommand(action => Items.Remove(action),
canExecute => canExecute != null && Items.Contains(canExecute)); }
}
So whatever view model has your Tabs collection should have an AddTabCommand and a CloseTabCommand that add and remove items from the Tabs collection. But just to be clear, for this to work properly, your ClosableTab class should be a data class and not a UI control class. Use a DataTemplate to specify it if it is a UI control.
You can find out about the RelayCommand from this article on MSDN.

WPF set DataTemplate on Viewmodel class programmatically

I have a listbox in WPF that will contain a list of ResultsViewModel items, however the actual runtime type of these objects could be
CalculateResultsViewModel,
ScenarioResultsViewModel,
GraphResultsviewModel etc etc,
all of which extend the base abstract class ResultsViewModel.
Each of these view models should be rendered differently in the ListBox so needs a different DataTemplate. I can do that just with XAML easy enough. The difficulty is that when the viewmodels are either "processing" or when they have failed", I need them to display a DataTemplate for "processing" or "errored" which I can only so far do with Triggers. That however then means I can't use the DataTemplateSelector or a basic XAML style.
The only solution I can think of (not clean I know) is to set the DataTemplate programmatically in the SetResult() method of each viewmodel class, which is what gets called when the processing completes either successfully or with an error. In that DependencyProperty I can look at the return code and then programatically set the DataTemplate depending on the sucess/failure result. The only problem is I cannot figure out how to
Obtain a DataTemplate resource from a ResourceDictionary just using c# code - bearing in mind Im calling all of this from the viewmodel class, not the window code-behind .xaml.cs file so it doesn't have access to the properties of Window
having only a handle to the viewmodel class, somehow obtain a reference to the ListBoxItem that contains it and then programmatically set the DataTemplate on this container.
Can anyone point me in the right direction?
you can take the magic with implicit datatemplates
<ListBox ItemSource={Binding YourResults}>
<ListBox.Resources>
<DataTemplate DataType={x:Type CalculateResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type ScenarioResultsViewModel}>
<Grid></Grid>
</DataTemplate>
<DataTemplate DataType={x:Type GraphResultsviewModel }>
<Grid></Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>
for "processing" or "errored" viewmodels you can specify a adorner overlay in all yout datatemplates (ok but you must use the triggers)
hope this helps

Looking for guidance for where to put some code in a WPF MVVM application

First time attempting MVVM, looking for clarity on where to put some code.
My main view will need to bind to a list that will be holding 1 to many UserControls.
Would the List exist in the ViewModel or the Model? From what I'm reading, the model contains properties typically that the View binds to via the ViewModel. I don't see how that would work for this, the Model would not need to know about the List of UserControls which is a list of a View(UserControl), I may be making this harder than needed, but I'm still wrapping my mind around where code should be placed and I want to understand it. Thanks for any guidance, or if I did not explain myself well please let me know.
Your UserControls should have a ViewModel (Let's call it ItemViewModel by now).
Your MainViewModel should have an ObservableCollection<ItemViewModel>.
Then your view should have an ItemsControl (or one of its derivatives such as ListBox) for which the ItemsSource property will be bound to the ObservableCollection.
And then the ItemsControl should define an ItemTemplate that contains your UserControl.
This is the right way to do what you're describing with WPF / MVVM.
Example (pseudocode):
MainViewModel:
public class MainViewModel
{
public ObservableCollection<ItemViewModel> Items {get;set;}
}
MainView:
<Window>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<my:UserControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Keep in mind that each instance of UserControl will have it's DataContext set to the corresponding item in the source collection.
Therefore, if your ItemsViewModel looks something like this:
public class ItemsViewModel
{
public string LastName {get;set;}
public string FirstName {get;set;}
//INotifyPropertyChanged, etc.
}
your UserControl can be defined like this:
<UserControl>
<StackPanel>
<TextBox Text="{Binding LastName}"/>
<TextBox Text="{Binding FirstName}"/>
</StackPanel>
</UserControl>
You shouldn't need a list of UserControls. What you would likely have is your View binding to a List of items in your ViewModel. For example, create a ListBox and set it's ItemsSource to your ViewModel's list.
To create a your user control for each item, you would need to create a DataTemplate for the type in your list and specify your UserControl and you can give any bindings inside that usercontrol to the item.
The ListBox will then use the DataTemplate to create a UserControl for each item in the list.

Categories

Resources