WPF C# How can I use a common ObservableCollection in two ViewModels? - c#

I got a question about how can I use a common ObservableCollection in two ViewModels in WPF C#?
I got two views, first view consist of a a observablecollection of categories used in a combobox in some form. The second view is a window which enable adding, editing and removal of categories. Now I'm retrieving the categories for both views separately, but I want to combine and use a common observable collection, in order to get the new changes when I added, change or remove categories in the second view.
Both Views and viewmodels is controlled by a mainviewmodel:
public viewModel1 viewModel1 { get; set; }
public ViewModel2 ViewModel2 { get; set; }
public MainViewModel()
{
this.InitializeCommands();
this.viewModel1 = new viewModel1();
this.ViewModel2 = new ViewModel2();
this.ViewModel2.OnChangedCategory += (s, e) =>
{
this.viewModel1.Categories = GetCategories();
};
}
ViewModel for View1:
public ObservableCollection<Categories> GetCategories
{
get
{
if (this._getCategories == null)
{
this._getCategories = methods.GetCategories();
}
return this._getCategories ;
}
set
{
if (this._getCategories != value)
{
this._getCategories = value;
this.OnPropertyChanged("GetCategories");
}
}
}
ViewModel for View2:
public ObservableCollection<Categories> GetCategories
{
get
{
if (this._getCategories == null)
{
this._getCategories = methods.GetCategories();
}
return this._getCategories ;
}
set
{
if (this._getCategories != value)
{
this._getCategories = value;
this.OnPropertyChanged("GetCategories");
}
}
}
In theory, they are both identical, just two different observable collection in two viewmodels, I want to use the same observable collection in both views, if it's in the first or second ViewModel doesn't matter. But it makes maybe more sense if it's in viewmodel 2. Let's say if I decide have a common categories observable collection in ViewModel 2, how can Viewmodel 1 use this observable collection from ViewModel 2 and still be in sync upon changes?
How can I do this?

You could make it a resource and then any code could get at it.
The sample associated with this article has two pairs of usercontrols share two such collections and sounds fairly similar to your requirement.
https://social.technet.microsoft.com/wiki/contents/articles/29859.wpf-tips-bind-to-current-item-of-collection.aspx

Related

Updating a single ObservableCollection but show in 2 different ListViews

Suppose I have one ObservableCollection that I insert, update and delete from.
public ObservableCollection<Item> AllItems {get;set;}
The Item class (which implements INotifyPropertyChanged) looks like this:
public class Item
{
public int Id {get;set;}
public int Kind {get;set;}
public string Text {get;set;}
}
I have this bound in my View in one ListView. Everything works, meaning if I delete or add or updates Items in the ObservableCollection from my ViewModel, they are updating in my ListView as expected.
But now I need to split the ListView in 2 ListViews in my View. The first ListView must display all items where Item.Kind=1. And the other ListView must have all items where Item.Kind=2.
I don't mind binding to two different ObservableCollections from my ViewModel, but I would really like a solution where I can just do:
var item = new Item { Id=22, Kind=1, Text="SomeText" };
AllItems.Add(item);
AllItems.First(x => x.Id==22).Text = "SomeOtherText";
AllItems.Remove(item);
And then these operations are automatically reflected in the 2 ListViews. So in the above example, the new Item is only viewed, updated and removed from the first ListView (where Item.Kind=1).
Is that possible?
EDIT: I should say that I have tried to use two CollectionViewSources with the same ObservableCollection as source. But that fails and does not seem to be the correct solution.
create two ListCollectionViews with different filters - one for each ListView
public ICollectionView Items_1 { get; private set; }
public ICollectionView Items_2 { get; private set; }
public ViewModel
{
Items_1 = new ListCollectionView(AllItems);
Items_1.Filter = o => (o as Item).Kind == 1;
Items_2 = new ListCollectionView(AllItems);
Items_2.Filter = o => (o as Item).Kind == 2;
}

How should the ViewModel refer to its Models properties?

As the ViewModel has the job to "prepare" the Model's properties to get displayed in the View, what is the best way of referring to the underlying Models properties from the ViewModel?
I could think about two solutions by now:
Option 1 - Duplicate the Model's properties in the ViewModel (wrapper-approach)
Architecture
class Model
{
public string p1 { get; set; }
public int p2 { get; set; }
}
class ViewModel : INotifyPropertyChanged
{
// Model-instance for this ViewModel
private Model M;
public string p1
{
get { return M.p1; }
set
{
M.p1 = value;
// assuming View controls are bound to the ViewModel's properties
RaisePropertyChanged("p1");
}
}
// let's say, I only want to check a Checkbox in the View,
// if the value of p2 exceeds 10.
// Raising the property changed notification would get handled
// in the modifying code instead of the missing setter of this property.
public bool p2
{
get
{
if (M.p2 > 10)
{ return true; }
else
{ return false; }
}
}
// Initialize the Model of the ViewModel instance in its c'tor
public ViewModel()
{ M = new Model(); }
}
Binding
<Textbox Text="{Binding p1}"/>
<Checkbox IsEnabled="False" IsChecked="{Binding p2, Mode=OneWay}"/>
Advantages
Full control about how the Model's properties are displayed on the View as shown in p2: int gets converted to bool on demand.
Changes of the properties of the ViewModel could be raised individual, might be a little performance increase compared to option 2.
Disadvantages
Violation of DRY.
More Code to write/maintain.
Modifications to the Model/ViewModel could easily become shotgun surgery.
Option 2 - Treat the whole Model as property of the ViewModel
Architecture
class Model
{
public string p1 { get; set; }
public int p2 { get; set; }
}
class ViewModel : INotifyPropertyChanged
{
// Model instance for this ViewModel (private field with public property)
private Model _M;
public Model M
{
get { return _M; }
set
{
_M = value;
// Raising the changing notification for the WHOLE Model-instance.
// This should cause ALL bound View-controls to update their values,
// even if only a single property actually got changed
RaisePropertyChanged("M");
}
}
// Initialize the Model of the ViewModel instance in its ctor
public ViewModel()
{ M = new Model(); }
}
Binding
<Textbox Text="{Binding M.p1}"/>
<Checkbox IsEnabled="False" IsChecked="{Binding M.p2, Mode=OneWay, Converter={StaticResource InverseBooleanConverter}"/>
Advantages
Can save a lot of code.
Reduces complexity.
Increases maintainability.
Disadvantages
In this approach, the ViewModel is nothing more than a continuous-flow water heater for the Models properties, except for some possible interaction logic for the View.
No control about how the Model's properties are displayed in the View - which ultimately leads to total needlessness of the ViewModel and implementation of conversion logic in the View.
It is the responsibility of your ViewModel to expose the Model to the View, you should not expose the Model's properties as additional properties in the ViewModel, instead, your View should bind directly to the model.
Additionally, it isn't wrong to have logic in your Model, in fact, it makes more sense to contain model related code within the model, as opposed to the ViewModel.
Here is an example:
public class Movie
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
//Notify property changed stuff (if required)
//This is obviously a stupid example, but the idea
//is to contain model related logic inside the model.
//It makes more sense inside the model.
MyFavourite = value == "My Movie";
}
}
private bool _MyFavourite;
public bool MyFavourite
{
get { return _MyFavourite; }
set
{
_MyFavourite = value;
//Notify property changed stuff.
}
}
}
So to answer your question a little more directly, you should expose your model in the view model as a property.
public class ViewModel
{
private Movie _Model;
public Movie Model
{
get { return _Model; }
set
{
_Model = value;
//Property changed stuff (if required)
}
}
...
}
Therefore, your View will bind to the Model property, like you have already done so.
EDIT
In the example for casting to the type, you can implement a read-only property in your Model, like so:
public bool MyBool
{
get
{
return MyInt > 10; }
}
}
Now the magic here would be that you will need to call the INotifyPropertyChanged for this property whenever MyInt changes. So your other property would look something like this:
public int MyInt
{
get { ... }
set
{
_MyInt = value;
//Notify property changed for the read-only property too.
OnPropertyChanged();
OnPropertyChanged("MyBool");
}
}
In my view, Model should not have RaisePropertyChanged stuff. Some view models (e.g. Blazor) might not need it, others (e.g. WPF) might use other mechanisms like DependencyProperty. Thus, to me Model is a POCO class. Hence, it becomes ViewModel responsibility to report changes to the data up to the View. Consequently, ViewModel is bound to wrap Model's properties (OA's option 1).
You might want to look at AutoMapper to centralize the mappings.

Master/detail view using TreeView

I'm working on implementing a master/details view in my application using a TreeView and a custom details view control. I'm also trying to stick to the MVVM pattern.
Right now the TreeView is bound to a collection of view model objects that contain all of the details and the details view is bound to the selected item of the TreeView.
This works great... until one of the TreeView nodes has 5,000 children and the application is suddenly taking up 500MB of RAM.
Main window view model:
public class MainWindowViewModel
{
private readonly List<ItemViewModel> rootItems;
public List<ItemViewModel> RootItems { get { return rootItems; } } // TreeView is bound to this property.
public MainWindowViewModel()
{
rootItems = GetRootItems();
}
// ...
}
Item view model:
public ItemViewModel
{
private readonly ModelItem item; // Has a TON of properties
private readonly List<ItemViewModel> children;
public List<ItemViewModel> Children { get { return children; } }
// ...
}
Here's how I'm binding the details view:
<View:ItemDetails DataContext="{Binding SelectedItem, ElementName=ItemTreeView}" />
I'm fairly new to WPF and the MVVM pattern, but it seems like a waste to I want to bind the TreeView to a collection of a smaller, simplified object that only has properties necessary for displaying the item (like Name and ID), then once it is selected have all of the details loaded. How would I go about doing something like this?
Overview
This should be a simple matter of binding the TreeView's selected item property to something on your source. However, because of the way the TreeView control was built, you have to write more code to get an MVVM-friendly solution, using out-of-the-box WPF.
If you're using vanilla WPF (which I'm assuming you are), then I'd recommend going with an attached behavior. The attached behavior would bind to an action on your main view model that would be invoked when the TreeView's selection changes. You could also invoke a command instead of an action, but I'm going to show you how to use an action.
Basically, the overall idea is to use one instance of your details view model that will be made available as a property of your master view model. Then, instead of your RootItems collection having hundreds of instances of view models, you can use light-weight objects that simply have a display name for the node and perhaps some kind of id field behind them. When the selection on your TreeView changes, you want to notify your details view model by either calling a method or setting a property. In the demonstration code below, I'm setting a property on the DetailsViewModel called Selection.
Walkthrough with Code
Here's the code for the attached behavior:
public static class TreeViewBehavior
{
public static readonly DependencyProperty SelectionChangedActionProperty =
DependencyProperty.RegisterAttached("SelectionChangedAction", typeof (Action<object>), typeof (TreeViewBehavior), new PropertyMetadata(default(Action), OnSelectionChangedActionChanged));
private static void OnSelectionChangedActionChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var treeView = sender as TreeView;
if (treeView == null) return;
var action = GetSelectionChangedAction(treeView);
if (action != null)
{
// Remove the next line if you don't want to invoke immediately.
InvokeSelectionChangedAction(treeView);
treeView.SelectedItemChanged += TreeViewOnSelectedItemChanged;
}
else
{
treeView.SelectedItemChanged -= TreeViewOnSelectedItemChanged;
}
}
private static void TreeViewOnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var treeView = sender as TreeView;
if (treeView == null) return;
InvokeSelectionChangedAction(treeView);
}
private static void InvokeSelectionChangedAction(TreeView treeView)
{
var action = GetSelectionChangedAction(treeView);
if (action == null) return;
var selectedItem = treeView.GetValue(TreeView.SelectedItemProperty);
action(selectedItem);
}
public static void SetSelectionChangedAction(TreeView treeView, Action<object> value)
{
treeView.SetValue(SelectionChangedActionProperty, value);
}
public static Action<object> GetSelectionChangedAction(TreeView treeView)
{
return (Action<object>) treeView.GetValue(SelectionChangedActionProperty);
}
}
Then, in the XAML on your TreeView element, apply the following: local:TreeViewBehavior.SelectionChangedAction="{Binding Path=SelectionChangedAction}". Note that you will have to substitute local for the namespace of the TreeViewBehavior class.
Now, add the following properties to your MainWindowViewModel:
public Action<object> SelectionChangedAction { get; private set; }
public DetailsViewModel DetailsViewModel { get; private set; }
In your MainWindowViewModel's constructor, you need to set the SelectionChangedAction property to something. You might do SelectionChangedAction = item => DetailsViewModel.Selection = item; if your DetailsViewModel has a Selection property on it. That's entirely up to you.
And finally, in your XAML, wire the details view up to its view model like so:
<View:ItemDetails DataContext="{Binding Path=DetailsViewModel}" />
That's the basic architecture of an MVVM friendly solution using straight WPF. Now, with that said, if you're using a framework like Caliburn.Micro or PRISM, your approach would probably be different than what I've provided here. Just keep that in mind.

RaisePropertyChanged doesn't work for collections

I'm using mvvm-light and I noticed this strange behavior about the RaisePropertyChanged.
xaml:
<ListBox ItemsSource="{Binding Collection}"/>
<TextBlock Text="{Binding Text}"/>
Observable class:
public class A : ObservableObject
{
private string _b;
public string B
{
get { return this._b; }
set
{
this._b = value;
this.RaisePropertyChanged("B");
}
}
}
vm:
public MainViewModel(IDataService dataService) { this.Collection = new List<A>(...); }
public RelayCommand Command1
{
get
{
return this._command1 ?? (this._command1= new RelayCommand(() =>
{
this.Collection.Add(new A());
this.Collection[2].B = "updated";
this.RaisePropertyChanged("Collection");
this.RaisePropertyChanged("Text");
}));
}
}
public RelayCommand Command2
{
get { return this._command2?? (this._command2 = new RelayCommand(() => { this.Text++; })); }
}
public List<A> Collection { get; set; }
public int Text { get; set; }
So, RaisePropertyChanged("Collection") doesn't update the binding while RaisePropertyChanged("Text") do. I can see it by executing the Command2 several times and the Command1 after that. If the Collection is an ObservableCollection then new element shows in a view, but updated item isn't, which means an internal mechanism of an ObservableCollection works, but not the RaisePropertyChanged.
First, an explanation of the issue:
On Windows Phone, when setting a value for a dependency property, the framework internally check if the new value is different from the old one (for optimization purpose maybe). When you raise the PropertyChanged event or directly re-assign your collection to the ItemsSource property (which is just a wrapper around the ItemsControl.ItemsSourceProperty dependency property), the framework detects that the value actually didn't change and doesn't update the property. Therefore, the ListBox is never notified of your changes, and isn't updated.
The ObservableCollection works because it uses a whole different mechanism: the ListBox directly subscribes to the CollectionChanged event of your collection, and thus isn't hindered by the limitations of the dependency properties.
Now, how to get around this limitation? The only workarounds I can think of are:
Use an ObservableCollection instead of a List
Assign null to the ItemsSource property of your ListBox, then re-assign your collection
Bind the ListBox to a property that will return a different collection every time it's called:
public List<A> CollectionCopy
{
get
{
return this.Collection.ToList();
}
}

How to Bind Listbox in WPF to a generic list?

i'm having trouble getting a clear answer for this.
I have a Static class (DataHolder) that holds a static list with a complex type (CustomerName and CustomerID properties).
I want to bind it to a ListBox in WPF but add another item that will have the word "All" for future drag and drop capablilities.
Anyone?
Create a ViewModel Class you can databind to! The ViewModel can reference the static class and copy the items to its own collection and add the all item to it.
Like this
public class YourViewModel
{
public virtual ObservableCollection<YourComplexType> YourCollection
{
get
{
var list = new ObservableCollection<YourComplexType>(YourStaticClass.YourList);
var allEntity = new YourComplexType();
allEntity.Name = "all";
allEntity.Id = 0;
list.Insert(0, allEntity);
return list;
}
}
}
Note, sometimes, you need empty Items. Since WPF can't databind to null values you need to use the same approach to handle it. The empty business entity has been a best practice for it. Just google it.
That "All" item has to be part of the list you bind your ListBox against. Natuarally you can not add that item to the DataHolder list because it holds items of type Customer (or similar). You could of course add a "magic" Customer that always acts as the "All" item but that is for obvious reasons a serious case of design smell (it is a list of Customers after all).
What you could do, is to not bind against the DataHolder list directly but introduce a wrapper. This wrapper would be your ViewModel. You would bind your ListBox agains a list of CustomerListItemViewModel that represents either a Customer or the "All" item.
CustomerViewModel
{
string Id { get; private set; }
string Name { get; set; }
public static readonly CustomerViewModel All { get; private set; }
static CustomerViewModel()
{
// set up the one and only "All" item
All = new CustomerViewModel();
All.Name = ResourceStrings.All;
}
private CustomerViewModel()
{
}
public CustomerViewModel(Customer actualCustomer)
{
this.Name = actualCustomer.Name;
this.Id = actualCustomer.Id;
}
}
someOtherViewModel.Customers = new ObservableCollection<CustomerViewModel>();
// add all the wrapping CustomerViewModel instances to the collection
someOtherViewModel.Customers.Add(CustomerViewModel.All);
And then in your Drag&Drop code somewhere in the ViewModel:
if(tragetCustomerViewModelItem = CustomerViewModel.All)
{
// something was dropped to the "All" item
}
I might have just introduced you to the benefits of MVVM in WPF. It saves you a lot of hassle in the long run.
If you use binding than the data provided as the source has to hold all of the items, ie. you can't databind and then add another item to the list.
You should add the "All" item to the DataHolder collection, and handle the 'All' item separately in your code.

Categories