Binding to collections of ViewModels or business models? - c#

please help me understand MVVM better:
I had been binding listboxes to lists of custom objects, but then I ran into some trouble with having selected items populate / bind correctly in a multi select list.
The solution I found used a List just for that list box, (or at least that's what I understood) and then the viewmodel class could have an IsSelected property, which worked for me.
My questions are this: Is this common to bind a listbox to a list of view models? it seems so strange, and if ti's normal, is binding a list box to a list of business objects bad MVVM technique?
Finally, when I bind a listbox to this list of viewmodels, how typically are those linked back to the list of business objects that they represent? do I keep an id in the view model, or a reference to the business object itself? Sorry for these questions, but I am trying to learn MVVM and do it right.
here is the viewmodel I am putting in a list and binding to the listbox:
class ItemViewModel
{
public ItemViewModel(string name)
{
Name = name;
}
public string Name { get; private set; }
bool isSelected;
public bool IsSelected
{
get {
return isSelected;
}
set {
isSelected = value;
}
}
}
here is the xaml for the listbox in the view
<ListBox Height="401" ItemsSource="{Binding Path=Users}" ItemTemplate="{StaticResource listBoxTemplate}" SelectionMode="Multiple" HorizontalAlignment="Left" Margin="202,29,0,0" Name="lbxAuthorizedUsers" VerticalAlignment="Top" Width="154" >
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>`
and here is the Users property in the view model
public List<ItemViewModel> Users
{
get { return this.users; }
set { this.users = value; }
}

What you have seems fine
The idea behind the MVVM design pattern is to keep your UI layer separate from the business logic layer. An ideal MVVM application can run with any UI (or no UI at all), so if you have some kind of business logic based off Selected Users, then you should either have a SelectedUsers collection on your ViewModel, or an IsSelected property on your User object.
As a side note, if you want WPF to automatically update it's UI when a property in your ViewModels or Models change, make sure they implement INotifyPropertyChanged. And if you want them to automatically update when a collection changes, make sure to use an ObservableCollection instead of a List
I started learning MVVM with this article by Josh Smith, which is a great introduction to the design pattern although when I was first starting out I had a hard time understanding it. If you're looking for something simpler, you can check out the simple MVVM example I wrote

Also, your ViewModel should implement INotifyPropertyChanged so that it can notify the View of any changes.

Related

Is it MVVM friendly to use other ViewModels as bindable properties?

First a little backstory: I am studying Xamarin for a month now, and I am about to start my first project.
I have a need where I have like 4 nested "generations" of a relational database, that I have to include in one View.
When I start to nest stuff, I am forced to move some Commands (ViewModel code) into the Model.
I want to avoid this at all cost, thus the question arise:
Is it MVVM friendly to use other ViewModels as bindable properties, like in the following Example?
ViewModels:
public class MainViewModel : FreshBasePageModel
{
public ObservableCollection<OtherViewModel> OtherCollection { get; set; }
}
public class OtherViewModel: FreshBasePageModel
{
public Command SomeCommand { get; set; }
}
And use it like this in the Views:
<ContentPage>
<ListView ItemsSource={Binding OtherCollection} SelectedItem={Binding SomeCommand}>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
This approach seems Okay to me, but this is my first ever MVVM project, and I wonder if this is how you do stuff.
I use FreshMvvm as the backing framework, and it uses conventions for bindings, so the view is automatically bound to its namesake partner.
Also, if you'd like to look at my nested lists, see below:
Thanks for your time,
By default this behaviour isn't supported because a control only has a DataContext o BindableContex and this class is where the compiler look for your binding properties and commands.
In MVVM, you should only use a ViewModel for each View so your approach isn't frequent.
However I think that you could use the code that you proposed if you use the Path binding property and you have other control which its BindableContex is OtherViewModel

i want to communicate from my gridcontrol to the the viewmodel of the view where it is hosted

I have a scenario where in i want to communicate between my gridcontrol ( which is a custom control of the infragistics xam grid control) to the viewmodel of the view that hosts this gridcontrol.
I just got into WPF and would like to know the best ways of solving this issue.
The structure is as follows
View
(Control)
||
View Model(of the view)
Your question is extremely vague, giving no indication as to what you are trying to bind too? I assume you are trying to bind to a property on the view model?
View - Its as simple as using DataBinding syntax.
<Grid ItemsSource="{Binding SomeCollection}">
...
</Grid>
ViewModel: Expose a property which implements INotifyPropertyChanged
private List<Something> someCollection;
public List<Something> SomeCollection
{
get
{
return this.someCollection;
}
set
{
this.someCollection = value;
this.NotifyOfPropertyChange(() => this.SomeCollection)
}
}
This is something you should be able to Google in seconds and find an answer, also I would expect that ANY WPF tutorial will start with an introduction to Data Binding.

What happens internally when you bind to ItemSource?

I'm curious how this works, because I have a MainViewModel, which has Property say called SubViewModel which has a Property of ObservableCollection (we'll call it Property1.)
I've implemented INotifyChangedProperty on everything.
My Main Window
<Window ..
DataContext="{Binding MainViewModel}" />
...
<StackPanel DataContext="{Binding SubViewModel}">
<local:SomeControl DataContext="{Binding}" />
</StackPanel>
</Window>
And my UserControl
<UserControl Name="SomeControl">
<DataGrid Name="MyDataGrid" ItemSource="{Binding Property1, Mode=TwoWay}" CurrentCellChanged="TestMethod" />
...
</UserControl>
In my test method, just as a test to figure out why the changes are not propegating up to the main view model I do something like this
private void TestMethod()
{
var vm = this.DataContext as SubViewModel;
var itemSourceObservableCollection = MyDataGrid.ItemsSource as ObservableCollection<MyType>;
//I thought vm.Property1 would be equal to itemSourceObservableCollection
//but they are not, itemSourceObservableCollection shows the changes I've made
//vm.Property1 has not reflected any changes made, even though I though they were the same item
}
So I figured out that ItemSource must create a copy of the item you bind it to? I'm stuck here, how do manually notify the viewModel that this property has changed and it needs to update? I thought that was INotifyPropertyChanged's job?
I think part of my problem is I lack the understanding of how this kinda works internally. If anyone can point to a good blog post, or documentation to help me understand why my code isn't working the way I expected, that would be great.
1) No copy is made.
2) ObservableCollection will propogate changes made to the collection, not the items within the collection. So you'll see additions, deletions etc. but NOT property changes to items within the collection.
3) If you want to see changes made to individual items in the ObservableCollection, you need to implement INotifyPropertyChanged on those items.
There's actually TWO different issues here. What happens internally when you bind to a collection? AND why changes on the user surface are not propagated back to your View Model. Based upon what you wrote, the two issues are not connected, but let's take them one at a time...
For the first issue... When you bind a collection, the WPF binding engine creates a "CollectionView" class that mediates between your object store and the logical tree. You can, if needed, get a copy of the the "CollectionView" using a static method on CollectionViewSource...
var cvs = CollectionViewSource.GetDefaultView(MyCollectionOfThings);
There are several interesting properties in the result, and some of them contain write accessors which allow you to directory modify the CollectionView.
For the second issue... The business classes in your SubViewModel need to inherit from INotifyPropertyChanged such that changes are 'announced' via the WPF binding engine. Your VM should be a publisher, but can also be a subscriber. A property that participates in the INotifyPropertyChanged plumbing gets declared like this...
private string _name;
[Description("Name of the driver")]
public string Name
{
[DebuggerStepThrough]
get { return _name; }
[DebuggerStepThrough]
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
This code publishes changes, but can also subscribe to changes made on the user surface by setting the appropriate attributes in your Xaml.
Background reading: What is a CollectionView?
Also, Similar question

Binding properties of multiple tree views to the same ViewModel

I am working on a MVVM implementation, where i'll spawn multiple views (side by side) each containing a tree control.
each of the views will have a similar tree, with a copy of [almost] all the same items.
I would like to synchronize the IsExpanded property on all the view/TreeView's..
meaning, if i collapse one node, i would like all of them to collapse (and some goes for column widths etc).
One way to do this, would be to bind all views to the same viewmodel, and have a DependencyProperty on that ViewModel, and set up the binding as Two Way on each view. However, i need each view to be bound to a separate viewmodel so that it can display unique values. I just need to synchronize a few properties of the tree, such as IsExpanded and Width.
What would be the best approach here?
You can use Prism and EventAggregator service from it to exchange data between view models.
There's no reason you can't have different collections within a single ViewModel, if that is the best design option. Especially if your multiple Trees / Collections are filtered from some 'complete set'; it might actually make more sense.
Just add multiple collections to your ViewModel, and bind to them.
public class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<MyItem> FirstTreeCollection
{
get
{
// whatever you need to do here
}
}
public ObservableCollection<MyItem> SecondTreeCollection
{
get { /* etc */ }
set { /* etc */ }
}
// etc
public bool Collapsed
{
get;
set;
}
}
and your Views should bind accordingly
// in your first view that contains a tree
<UserControl x:Class="View1" ...>
<TreeView Name="FirstTree"
ItemsSource={Binding FirstTreeCollection}
Collapsed={Binding Collapsed} ... >
// & in your second view that contains a tree
<UserControl x:Class="View2" ...>
<TreeView Name="SecondTree"
ItemsSource={Binding SecondTreeCollection}
Collapsed={Binding Collapsed} ... >
To clarify, I'm suggesting that you use a single ViewModel for all of these Tree-containing Views.
The ViewModel won't need a DependencyPropery--it will just need to expose a property that implements INotifyPropertyChanged.
The two ViewModels will need to have some way of sharing state, and exposing a property that represents that state. How you share the state depends heavily on how your ViewModels are instantiated (and probably other factors). For example, if your two VMs are being instantiated by some parent object, the parent may create one instance and pass it to both VMs in their constructors.
If you display the treeview's using xaml, you can bind every treeview to the first treeview spawned.
For example you can use some binding like this:
<TreeView Name="FirstTreeView" />
<TreeView Name="SecondTree"
IsExpended = {Binding Path=IsExpanded, ElementName=FirstTreeView, Mode=TwoWay}/>

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