ObservableCollection Behavior in MVVM Child Instance - c#

I have a strange behavior going on. I'm using MVVM pattern, i have a binding to an Observable collection named AlarmCollection to a grid control in a View named AlarmView. When i create multiple instances of a AlarmModelView class, and add items to AlarmCollection, all the instances display the changes.
Any changes to the ObservableColelction AlarmCollection, affects all the bound ItemSources of the grid controls.
I have tried to lock the dispatcher thread, from a similar post here, to no avail.
Is there anyway to keep the changes to this Observable collection, within each instance of the ViewModel? So that each modification does not affect any other collection in the UI thread.
Any help is appreciated.
[edit below]
It is strange scenario, I need to zoom/drill into what is rendered by creating the new instances of the Child MV, which in turn adds tabs to the Parent MV. The Child Views are all bound to the same Collection names, and all are being updated by a WCF Async call. I need X number multiple instances, based on the how deep the zoom level goes, so i need 1 ModelView object.
How would i achieve this using CollectionChanged event or creating the ModelView's own CollectionView?
private MainViewModel _parentViewModel;
public MainViewModel ParentViewModel
{
get { return _parentViewModel; }
set
{
if (ParentViewModel == value) { return; }
SetPropertyValue(ref _parentCircuitViewModel, value, "ParentViewModel");
}
}
private ObservableCollection<DetailEntity> _alarmCollection;
public ObservableCollection<DetailEntity> AlarmCollection
{
get
{
if (_alarmCollection == null)
_alarmCollection = new ObservableCollection<DetailEntity>();
return _alarmCollection;
}
private set { _alarmCollection = value; }
}
ServiceNode _selectedNode;
public ServiceNode SelectedNode
{
get { return _selectedNode; }
set
{
SetPropertyValue(ref _selectedNode, value, "SelectedNode");
// render selected child node service path
RenderSubPath(_selectedNode);
// reset storage value
_selectedCircuitNode = null;
}
}
// Constructor
public RenderViewModel(string servicePath CircuitMainViewModel parentViewModel)
{
ServicePath = servicePath,
ParentCircuitViewModel = parentViewModel;
// event to handler for completed async calls
Client.GetAlarmsByNodeListCompleted += new EventHandler<GetAlarmsByNodeListCompletedEventArgs>(Client_GetAlarmsByNodeListCompleted);
}
void RenderSubPath(ServiceNode childNode)
{
if (childNode == null)
return;
// create a new child instance and add to parent VM tab
_parentViewModel.AddServiceRenderTab(new ViewModel.Workspaces.RenderViewModel(childNode.elementPath _parentViewModel);
}
// wcf async webservice call to add alarm to ObservableCollection
// ** This is updating all Collections in all Views.
void Client_GetAlarmsByNodeListCompleted(object sender, AlarmServiceReference.GetAlarmsByNodeListCompletedEventArgs e)
{
try
{
if (e.Result == null)
return;
// add to parent Netcool alarm collection
foreach (DetailEntity alarm in nodeAlarms)
{
_alarmCollection.Add(alarm);
}
}
}

From your description, it sounds as though all your views are bound to the same underlying collection. For any collection you bind to, WPF will actually bind to a collection view (ICollectionView) wrapped around that collection. If you don't explicitly create your own collection view, it will use a default one. Any binding to the same collection will result in the same collection view being used.
It's hard to say without seeing your code, but it's likely you want to either use a separate instance of the underlying view model (and, hence, the collection) or you want to explicitly create separate collection views and bind to them instead.

Related

How to inject view when selected item property changes in another view

I am trying to determine the best method to populate a region when the selected item of a data grid view changes.
I am using Prism.DryIoc in my WPF desktop application. It has several regions, each with corresponding loosely coupled views and viewmodels. When the selected item of this data grid changes, I want a different view to populate a different region.
These views do not have references to each other even though they are contained in the same module project file.
So far, I have tried using the IEventAggregator to publish that the selected item has changed.
public class MyObjectViewModel : BindableBase
{
private IEventAggregator _ea;
public MyObjectViewModel(IEventAggregator ea )
{
_ea = ea;
//Do stuff here …
Grid_SelectionChanged = new DelegateCommand(SelectObject, CanSelectObject);
}
private MyData _selectedObject;
public MyData SelectedObject
{
get { return _selectedObject; }
set
{
SetProperty(ref _selectedObject, value);
_ea.GetEvent<MyObjectSelectionChangedEvent>().Publish(SelectedObject);
}
} …
In my MyAppModule.cs file I have the following constructor where I subscribe to the event:
public MyAppModule(IRegionManager regionManager, IEventAggregator ea)
{
_regionManager = regionManager;
ea.GetEvent<MyObjectSelectionChangedEvent>().Subscribe(MyObjectSelectionChanged);
}
I have a corresponding method that reads a property from SelectedObject.ObjectName.
private void MyObjectSelectionChanged(MyObject myObject)
{
Type myObjectType = null;
switch (myObject.ObjectName)
{
case "objectA":
myObjectType = typeof(Views.MyObjectA);
break;
case "objectB":
myObjectType = typeof(Views.MyObjectB);
break;
...
default:
myObjectType = typeof(Views.NoObjects);
break;
}
_regionManager.AddToRegion("TestDataRegion", deviceType);
}
When I debug this, the publish event is called but the above method is not. The subscribe method is called when the object is created.
Am I pursuing the correct solution in order to dynamically load a view based on a selected item in the datagrid object?
What am I missing to get my method called after publishing that the event changed?
Is there a better way to handle this in Prism so that I don't have to have a dirty setter?
EDIT: I have switched to use RegisterForNavigation and RequestNavigation. Thank you for the tip

Working with an ObservableCollection based on an Entity Framework one to many property

Working with WPF in MVVM. I have a ViewModel with a CurrentItem property. This is an Item object which is pulled directly from Entity Framework. Item has a collection of Property objects.
public virtual ICollection<Property> Properties { get; set; }
In the View, I need users to be able to add and remove objects from this collection. To do that, I need to create an ObservableCollection<Property>, which we'll call ItemProperties.
There are various ways to do this. The most obvious is to add a ObservableCollection<Property> property on the ViewModel. Then populate this in the constructor, like so:
ItemProperties = new ObservableCollection<Property>(CurrentItem.Properties);
It's also possible to create an ObservableCollection wrapper that sits over the top of the real collection:
public ObservableCollection<Property> ItemProperties
{
get
{
return new ObservableCollection<Property>(CurrentItem.Properties);
}
set
{
CurrentItem.Properties = value.ToList();
OnPropertyChanged("ItemProperties");
}
}
Which has its own problems. You can't just Add() to this collection, since it'll get first, meaning the collection remains unchanged. So you'd either have to spin up a new collection, add to that, and then assign its value to the property or raise the OnPropertyChanged event outside the property. Either of which also sounds like a maintenance issue.
Is there a more effective way of doing this, which allows you to access the EF property list directly?
On this you have advantage of decoupling between data layer and Presentation , No need to spin up the collection.
Try a LoadedEvent to load data from the server.
Sample event is below
private ObservableCollection<Property> _itemProperties;
public ObservableCollection<Property> ItemProperties
{
get { return _itemProperties; }
set
{
_itemProperties= value;
RaisePropertyChanged(() => ItemProperties);
}
}
The loaded event
var result= await Task.Run(() => MyBusiness.GetMyData());
//Map to the viewModel if necessary
ItemProperties = result;
Add to the collection
var isSuccess = await Task.Run(()=>MyBusiness.Insert(x));
if(isSuccess)
{
ItemProperties.Add(x);
}
If you have access to your DbContext in your ViewModel class, you can use DbSet<TEntity>.Local property which it will give you an ObservableCollection<TEntity> that contains all Unchanged, Modified and Added objects that are currently tracked by the DbContext for the given DbSet, but first you need to filter to load into memory only the PropertyItems that belong to your CurrentItem.
public class YourViewModel
{
private context=new YourContext();
public YourViewModel()
{
context.ItemProperties.Where(ip=>ip.ItemId==CurrentItem.Id).Load();
ItemProperties=context.ItemProperties.Local;
}
private ObservableCollection<Property> _itemProperties;
public ObservableCollection<Property> ItemProperties
{
get { return _itemProperties; }
set
{
_itemProperties= value;
OnPropertyChanged("ItemProperties");
}
}
public void SaveItemProperties()
{
context.SaveChanges();
}
}
To save the changes the only you need to do is create, for example, a command that calls the SaveItemProperties method. Also, it could be a good idea disable lazy loading to not load twice the ItemProperties related to your CurrentItem.
If you need to understand more about how this works you can read this article.
either way is good. But what you need to do is to define an handler to the event CollectionChanged present in the Observable Collection. Your underlying entity must have a default constructor too. So when the new item will be created in the grid, that event will be raised.
_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e){if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
}
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add
}
First of all, I don't think creating an ObservableCollection for every get is a good idea. Instead I would cache it in a field. Second, for the cached instance, you will probably want to subscribe to CollectionChanged event in which you will changes will be persisted to the underlying collection.

How to use ObservableCollection databinding?

Hello all i am pretty new to c# mvvm or data binding. I saw some examples using
ObservableCollection As explanined, An ObservableCollection is a dynamic collection of objects of a given type. Objects can be added, removed or be updated with an automatic notification of actions.
I have the following code: a property of type ObservableCollection<StudentViewModel> but it also implements the INotifyPropertyChanged interface. So why it needs to implement the INotifyPropertyChanged interface with type ObservableCollection here ? why not just automatic notification?
public ObservableCollection<StudentViewModel> TheStudents
{
get
{
return _theStudents;
}
set
{
if (_theStudents == value)
return;
if (_theStudents != null)
{
foreach (var StudentViewModel in _theStudents)
{
DisconnectStudentViewModel(StudentViewModel);
}
}
_theStudents = value;
if (_theStudents != null)
{
foreach (var StudentViewModel in _theStudents)
{
ConnectStudentViewModel(StudentViewModel);
}
}
OnPropertyChanged("TheStudents");
}
}
more background: simply to say when click button, the following function GetStudentsAction() will trigger------>TheStudents = sth in turn should told the VIEW that property has changed.
public void GetStudentsAction()
{
TheStudents = GetStudentsDelegate();
IsSaveStudentsActionEnabled = true;
IsAddStudentsActionEnabled = true;
}
The ObservableCollection class sends notifications when the contents of the collection change, such as adding or removing an item. These are special "this collection has changed" events that WPF can listen for (as defined in INotifyCollectionChanged).
It does not send notifications when you swap out the collection itself for a new instance of the collection. That's the responsibility of the object that has the collection as a property. This is the same event you have to send whenever any property on your view model has changed (as defined in INotifyPropertyChanged).
As a rule of thumb, it's rarely a good idea to have collection properties that can be set externally, the way you're using them. Typically you would give your collection property a private setter, and only allow external classes to add, remove, and clear the items from it. That eliminates the need to have a notification if your collection changes: you instantiate it once, in the constructor, and it never changes after that.
(That advice may or may not apply to your situation, but typically when I see collection types with public setters, it indicates a flaw in the design.)

Does an "Atomic Move" operation exist for C# collections?

Lets say I have a ViewModel in a WPF application with an ObservableCollection. This collection is synchronized with a Model collection so that updating either one updates the other. The model has a business rule that states it must contain 2 Thing's at all times, but the order in the collection can be changed. The user will see a list that has two Thing's in it, and has buttons they can click to move a Thing up or down. The model class subscribes to it's collection's change handler and throws an exception if there are ever less than 2 Things in the collection.
Something like this (very simplified, edge cases not handled etc)
public class ViewModel
{
private Model _model;
public ObservableCollection<Thing> Things { get; set; }
public Thing SelectedThing { get; set; }
public ViewModel(Model model)
{
_model = model;
Things = new ObservableCollection<Thing>(_model.Things);
// Some other code that ensures the model and viewmodel collections stay in sync
// ...
}
public void MoveUp()
{
var selected = SelectedThing;
int i = Things.IndexOf(selected);
Things.Remove(selected); // Throws exception
Things.Insert(i - 1, selected);
}
}
public class Model
{
public ObservableCollection<Thing> Things { get; set; }
public Model()
{
Things = new ObservableCollection
{
new Thing(),
new Thing()
};
Things.CollectionChanged += CollectionChangedHandler;
}
private void CollectionChangedHandler(object sender, CollectionChangedEventArgs e)
{
if(e.Action == CollectionChangedAction.Remove)
{
if(Things.Count < 2)
{
throw new YouCantDoThatException();
}
}
}
}
Basically, when a user clicks "Move Up", the selected item is temporarily removed from the collection and inserted above where it was before. This is an invalid state for the model though, and I can't change the model because it's an API for a lot of other things. Is there a way in C# to do an "Atomic Move" operation that will allow me to move the item without first removing it?
I see two options:
Use the "Move" function provided by ObservableCollection. The disadvantage is that your code is in a state with just one item (albeit for a very short amount of time), but your exception will not throw since CollectionChanged isn't raised until the move is complete.
Reverse your logic, and perform the insert before the remove. You could do this with the InsertItem(index, item) function, followed by the RemoveAt(index) command.
In general, there is no such thing as an "Atomic" move because of how "swap" operations work. You need to assign the "first" value to a temp value, assign the second to the first, and then assign the temp value to the second. Certain assembly operations can make this "Atomic" in the purest sense, but that won't help with NotifyCollectionChanged.
As an aside, you can derive from ObservableCollection and implement your own move algorithm, which you shouldn't need to do unless you need Move to have additional functionality. (MSDN).

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.

Categories