Bubbling NotifyOfPropertyChange with embedded classes - c#

So here's a question that has me running in circles. I am working with an embedded class structure which needs to keep its child objects private but should be able to pass certain NotifyOfPropertyChange events up the ladder from data in those child objects. What is the best way to do this.
My current approach is the code below where my view for SystemViewModel (SystemView) has an element bound to the CommunicationStatus property, and I have a parent class SystemViewModel that has child class CommunicationManager which has child class Communicator as follows.
Things that make it difficult:
1) It MUST be assumed in this case that Communicator has no visibility of SystemViewModel so putting a NotifyOfPropertyChanged(() => CommunicationStatus) in the set method of Communicator's Connected property should not be an option... unless I'm missing something obvious.
2) SystemViewModel should not be able to access Communicator directly so binding from SystemView.xaml to Connected can't be done.
In my mind the NotifyOfPropertyChanged event in Connected should bubble up to the parents due to the implementation of PropertyChangedBase in all classes but that's not happening. Would love any help!
public class SystemViewModel : PropertyChangedBase
{
private CommunicationManager CommunicationManager;
public string CommunicationStatus
{
get
{
if (CommunicationManager.YepConnected)
{
return "Green";
}
else
{
return "Red";
}
}
}
}
public class CommunicationManager : PropertyChangedBase
{
private Communicator Communicator;
public bool YepConnected { get { return Communicator.Connected; } }
}
public class Communicator: PropertyChangedBase
{
private bool _connected;
public bool Connected
{
get { return _connected; }
set
{
_connected = value;
NotifyOfPropertyChange(() => Connected);
}
}
}
EDIT
So it appears that this works correctly and propagates the event as expected from the child class to the parent class. The real issue, which was a bit more insideous, has to do with how the WPF Binding relates to the property. Just for reference, the XAML I'm using looks like this:
<TextBlock Text="Status" Background="{Binding CommunicationStatus}"/>
Also, I used SolidColorBrush instead of string (although they both bind the same and work).
The issue is that when the notification event propagates up from Connected to CommunicationStatus, it stops there and does not propagate to the XAML binding (Nowhere in my code is CommunicationStatus used except in the XAML binding). I know the binding works because by debug I observe that when the program runs initially the color is set to red upon execution of the CommunicationStatus get method, presumably called from the XAML binding. Once the code is running, CommunicationStatus does update whenever Connected does, but the XAML binding no longer observes that change. If I manually implement NotifyOfPropertyChange(() => CommunicationStatus);, the binding element decides to update. However, because I'm not using any sort of set method in CommunicationStatus (and the notify event doesn't propagate up), there doesn't seem to be a straight-forward way of informing the XAML that my value has changed.
Sketchy Solution: Watch for changes to CommunicationStatus and raise the NotifyOfPropertyChange(() => CommunicationStatus); event as follows:
public class SystemViewModel : Conductor<object>
{
private CommunicationManager CommunicationManager;
private SolidColorBrush LastCommunicationStatusValue = new SolidColorBrush();
public SolidColorBrush CommunicationStatus
{
get
{
SolidColorBrush CurCommunicationStatusValue;
if (CommunicationManager.YepConnected)
{
CurCommunicationStatusValue = new SolidColorBrush(Colors.Green);
}
else
{
CurCommunicationStatusValue = new SolidColorBrush(Colors.Red);
}
if (CurCommunicationStatusValue.Color != LastCommunicationStatusValue.Color)
{
LastCommunicationStatusValue = CurCommunicationStatusValue;
NotifyOfPropertyChange(() => CommunicationStatus);
}
return CurCommunicationStatusValue;
}
}
}
And yes, if you don't do it perfectly it's an instant Stack Overflow (pun intended :)
Whenever the value of Connected changes, I observe that CommunicationStatus's get method executes. By doing this, that execution results in another execution of the get method, only this time the XAML updates.
Can anyone explain why this solution works and/or offer a more eloquent solution?

Here is an example how to do that with ReactiveUI
public class SystemViewModel : ReactiveObject
{
private readonly CommunicationManager communicationManager;
private readonly ObservableAsPropertyHelper<string> connectionStatus;
public SystemViewModel( CommunicationManager communicationManager )
{
this.communicationManager = communicationManager ?? throw new ArgumentNullException(nameof(communicationManager));
this.communicationManager
.WhenAnyValue( e => e.YepConnected, state => state ? "Green" : "Red" )
.ToProperty( this, e => e.ConnectionStatus, out connectionStatus );
}
public string ConnectionStatus => connectionStatus.Value;
}
public class CommunicationManager : ReactiveObject
{
private readonly Communicator communicator;
private readonly ObservableAsPropertyHelper<bool> yepConnected;
public CommunicationManager(Communicator communicator)
{
this.communicator = communicator ?? throw new ArgumentNullException(nameof(communicator));
this.communicator
.WhenAnyValue( e => e.Connected )
.ToProperty( this, e => e.YepConnected, out yepConnected );
}
public bool YepConnected => yepConnected.Value;
}
public class Communicator : ReactiveObject
{
private bool _connected;
public bool Connected
{
get { return _connected; }
set { this.RaiseAndSetIfChanged( ref _connected, value); }
}
}
Simple test
var communicator = new Communicator();
var manager = new CommunicationManager(communicator);
var vm = new SystemViewModel( manager );
vm.PropertyChanged += (s,e) => Console.WriteLine( "SystemViewModel.{0} changed", e.PropertyName );
communicator.Connected = true;
communicator.Connected = false;
generated output
SystemViewModel.ConnectionStatus changed
SystemViewModel.ConnectionStatus changed

Related

How to bind two properties from different models in WPF

Let say I have classes like those:
public class ParentModel : INotifyPropertyChanged
{
// INotifyPropertyChanged pattern implemented ...
public IChildViewModel CurrentControlModel {
get { ... } set { /* Notify on changes */ }
}
}
public class ChildModelA : INotifyPropertyChanged, IChildViewModel
{
// INotifyPropertyChanged pattern implemented ...
public ICommand Command {
get { ... } set { /* Notify on changes */ }
}
}
public class ChildModelB : INotifyPropertyChanged, IChildViewModel
{
// INotifyPropertyChanged pattern implemented ...
public ICommand Command {
get { ... } set { /* Notify on changes */ }
}
}
public class ButtonViewModel : INotifyPropertyChanged
{
ICommand Command get { ... } set { /* Notify on changes */ }
}
I would like to have Command property to reflect the value of parentModelInstance.CurrentControlModel.Command event if
CurrentControlModel changes.
I cannot modify the ButtonViewModel.Command property to be a proxy of the property
because it's the view model for all buttons and I don't want to specialize it for every possible button.
If I do
ButtonViewModel viewModel;
viewModel.Command = parentModelInstance.CurrentControlModel.Command;
it doesn't work because CurrentControlModel can change (it's null at startup for instance).
I can listen to PropertyChanged event but it will cumbersome to do that for all properties of the model.
Any easier and cleaner alternative ?
Context
To give a bit of context, it's part of a dynamic toolbar code where you have buttons that can change icon, be disabled or change command, command target etc...
depending on what is the current focused control (which can be of different type).
CurrentControlModel is the view model of the current focused control.
The journey into the binding land
First solution: One helper to rule them all and with the View Model bind them
It was inspired by ReactiveUI and manual binding on DependencyProperty :
public static BindableProperty<TProperty> Watch<TInstance, TProperty>(
this TInstance instance,
Expression<Func<TInstance, TProperty>> expression,
BindingMode mode = BindingMode.TwoWay)
{
return new BindableProperty<TProperty>(instance,
GetPath((MemberExpression)expression.Body), mode);
}
public static void BindTo<TInstance, TProperty>(
this BindableProperty<TProperty> bindable,
TInstance instance,
Expression<Func<TInstance, TProperty>> expression) where TInstance
: DependencyObject
{
var getterBody = expression.Body;
var propertyInfo = (PropertyInfo)((MemberExpression)getterBody).Member;
var name = propertyInfo.Name;
var dependencyPropertyName = name + "Property";
var fieldInfo = typeof(TInstance).GetField(dependencyPropertyName,
BindingFlags.Public | BindingFlags.Static);
var dependencyProperty = (DependencyProperty)fieldInfo.GetValue(null);
Binding binding = new Binding();
binding.Source = bindable.Source;
binding.Path = new PropertyPath(bindable.Path);
binding.Mode = bindable.Mode;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
BindingOperations.SetBinding(instance, dependencyProperty, binding);
}
public class BindableProperty<T>
{
public object Source { get; }
public string Path { get; }
public BindingMode Mode { get; }
public BindableProperty(object source, string path, BindingMode mode)
{
Source = source;
Path = path;
Mode = mode;
}
}
ButtonViewModel must derive from DependencyObject and implement the pattern
for the Command property
public class ButtonViewModel : DependencyObject
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand),
typeof(ButtonViewModel), new PropertyMetadata(default(ICommand)));
public ICommand Command
{
get { return (ICommand) GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}
Then it can be used like this (for binding paste command to the paste button):
container.Watch(x => x.CurrentControlModel.Commands.Paste)
.BindTo(pasteButtonViewModel, x => x.Command);
Issues
Must setup DependencyProperty pattern for all of properties of view model.
Reflexion and expression analysis can raise runtime exceptions.
In case a conversion is needed, we must write a proxy doing the conversion and the value modification propagation.
Second solution: Reactive.UI and Fody
Reference the ReactiveUI.WPF and ReactiveUI.Fody, and modify the view model like this
public class ButtonViewModel : ReactiveObject
{
[Reactive]
public ICommand Command { get; set; }
}
Then we can bind the two properties like this:
container.WhenAnyValue(x => x.CurrentControlModel.Commands.Paste)
.BindTo(pasteButtonViewModel, x => x.Command);
Potential issue remaining
By not relying on DependencyProperty (apparently), there is a potential issue because we cannot tell the listener that the property is not set (with DependencyProperty.UnsetValue).
it's a one way binding.

Save information between VMs

The main idea what I am trying to do - to have one VM, which has a lot of other VMs.
The problem is to organize data transportation.
Main VM is connected with a template and other VMs have their own templates.
I use a navigator to change VMs and template selector to change templates.
Navigator:
public class NavigationController : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<ViewModelBase> _viewModels;
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel {
get { return _currentViewModel; }
set { _currentViewModel = value; OnPropertyChanged(nameof(CurrentViewModel)); }
}
private List<ViewModelBase> _legViewModels;
private ViewModelBase _legViewModel;
public ViewModelBase LegViewModel
{
get { return _legViewModel; }
set { _legViewModel = value; OnPropertyChanged(nameof(LegViewModel)); }
}
public NavigationController()
{
_viewModels = new List<ViewModelBase>
{
new ViewModelLogin(this),
new ViewModelPhysicalOverview(this),
...list of VMs...
};
_currentViewModel = _viewModels.First();
_legViewModels = new List<ViewModelBase>
{
new SFSViewModel(this),
new BPVHipViewModel(this)
};
_legViewModel = _legViewModels.First();
}
public void NavigateTo<T>()
{
var target = _viewModels.FirstOrDefault(e => e.GetType() == typeof(T));
if (target != null)
CurrentViewModel = target;
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
//если PropertyChanged не нулевое - оно будет разбужено
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My main VM:
public BPVHipViewModel LeftBPVHip { get; protected set; }
public SFSViewModel LeftSFS { get; protected set; }
public BPVHipViewModel RightBPVHip { get; protected set; }
public SFSViewModel RightSFS { get; protected set; }
public ViewModelAddPhysical(NavigationController controller) : base(controller)
{
LeftBPVHip = new BPVHipViewModel(Controller);
RightBPVHip = new BPVHipViewModel(Controller);
LeftSFS = new SFSViewModel(Controller);
RightSFS = new SFSViewModel(Controller);
Controller = controller;
base.HasNavigation = false;
ToRightBPVHipCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = RightBPVHip;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToLeftBPVHipCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = LeftBPVHip;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToLeftSFSCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = LeftSFS;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToRightSFSCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = RightSFS;
Controller.NavigateTo<LegPartViewModel>();
}
);
}
So before I go to another VM and change my screen, I do
Controller.LegViewModel = RightSFS;
and I thought if I change something in RightSFS - it will keep changes after returning to main VM. But I guess it doesn't work like this.
In children I have:
private bool _isEmpty = true;
public bool IsEmpty {
get
{
return _isEmpty;
}
protected set {
_isEmpty = value;
OnPropertyChanged("IsEmpty");
}
}
public string ButtonText
{
get
{
if (!IsEmpty) return "Edit";
else return "Fill";
}
}
And a fn that fires before I return to parent screen:
SaveCommand = new DelegateCommand(
() =>
{
IsEmpty = false;
Controller.NavigateTo<ViewModelAddPhysical>();
}
);
so I want a button from main template to show if we already have visited child screen, in this case I want "Edit" text. But it returns "Fill" all the time, 'cause IsEmpty doesn't change from true to false for him and I don't understand how to fix it. Please help.
For me, you are trying to invent a wheel of your own. It's done, multiple times. Every MVVM framework outthere has built-in navigation.
Take a look at ReactiveUI (great framework) samples, they are doing exactly what you need.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;
using ReactiveUI.Samples.Routing.Views;
using Splat;
namespace ReactiveUI.Samples.Routing.ViewModels
{
/* COOLSTUFF: What is the AppBootstrapper?
*
* The AppBootstrapper is like a ViewModel for the WPF Application class.
* Since Application isn't very testable (just like Window / UserControl),
* we want to create a class we can test. Since our application only has
* one "screen" (i.e. a place we present Routed Views), we can also use
* this as our IScreen.
*
* An IScreen is a ViewModel that contains a Router - practically speaking,
* it usually represents a Window (or the RootFrame of a WinRT app). We
* should technically create a MainWindowViewModel to represent the IScreen,
* but there isn't much benefit to split those up unless you've got multiple
* windows.
*
* AppBootstrapper is a good place to implement a lot of the "global
* variable" type things in your application. It's also the place where
* you should configure your IoC container. And finally, it's the place
* which decides which View to Navigate to when the application starts.
*/
public class AppBootstrapper : ReactiveObject, IScreen
{
public RoutingState Router { get; private set; }
public AppBootstrapper(IMutableDependencyResolver dependencyResolver = null, RoutingState testRouter = null)
{
Router = testRouter ?? new RoutingState();
dependencyResolver = dependencyResolver ?? Locator.CurrentMutable;
// Bind
RegisterParts(dependencyResolver);
// TODO: This is a good place to set up any other app
// startup tasks, like setting the logging level
LogHost.Default.Level = LogLevel.Debug;
// Navigate to the opening page of the application
// you can set any property of this new VM to transport data
Router.Navigate.Execute(new WelcomeViewModel(this));
}
private void RegisterParts(IMutableDependencyResolver dependencyResolver)
{
dependencyResolver.RegisterConstant(this, typeof(IScreen));
dependencyResolver.Register(() => new WelcomeView(), typeof(IViewFor<WelcomeViewModel>));
}
}
}
I used MessageBus pattern, it was perfect solution for me
class Subscription
{
public object Instance { get; set; }
public Action<object, object> Handler;
}
public class MessageBus
{
#region Singleton
private static readonly MessageBus _instance = new MessageBus();
public static MessageBus Default => _instance;
private MessageBus()
{
}
#endregion
private readonly Dictionary<string, List<Action<object, object>>> _hadlersMap
= new Dictionary<string, List<Action<object, object>>>();
public void Call(string name, object sender, object data)
{
List<Action<object, object>> handlers;
if(!_hadlersMap.TryGetValue(name.ToUpper(), out handlers))
return;
foreach (var handler in handlers)
{
handler?.Invoke(sender,data);
}
}
public void Subscribe(string name, Action<object, object> handler)
{
name = name.ToUpper();
List<Action<object, object>> handlers;
if (!_hadlersMap.TryGetValue(name, out handlers))
{
handlers = new List<Action<object, object>>{ handler };
_hadlersMap.Add(name, handlers);
}
else
{
handlers.Add(handler);
}
}
}
So I have never seem things be done this way. Have you looked at Windsor. I believe dependency injection and inversion of control could improve the scalability here. As far as suggestions go.
There is a lot of instantiation going on in many different places in the code here. Maybe creating a factory to handle all the new-ing up. IOC would help with that as well. You could place your list of models globally. App.Current.Properties[ "someVm" ] = vmInstance; if you are wanting to save the vm state.
Another way to persist the vm state would of course be to make that vm a singleton ensuring that when called it returns that only instance if already exists or instantiates if not.
Finally, I have persisted vm state upon unloading and reading state from somewhere upon loading. This is common and many default controls do this.

In MVVMCross, is it possible to close a viewmodel and pass values back to the previous viewmodel in the navigation stack?

Consider the following example. I have three view models, ViewModel_A, ViewModel_B, and ViewModel_Values.
I want to be able to navigate to ViewModel_Values from either ViewModel_A or ViewModel_B, select a value from ViewModel_Values, then return that value to the calling view model.
Is there a way of passing arguments to previous view models in the navigation stack so that I can simply call ViewModel_Values.Close(this), thereby ensuring that the ViewModels_Values is decoupled from any other view models and can be used with arbitrary "parent" view models?
MvvmCross 5 onwards
From MvvmCross 5 you can use the new IMvxNavigationService that allows you to have a much richer navigation. One of the new features is the possibility to await a value from another ViewModel after navigating to it and should be the approach to take after MvvmCross 5 instead of Messenger, e.g.:
public class ViewModel_A : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public ViewModel_A(IMvxNavigationService navigation)
{
_navigationService = navigationService;
}
public override async Task Initialize()
{
//Do heavy work and data loading here
}
public async Task SomeMethod()
{
var result = await _navigationService.Navigate<ViewModel_Values, MyObject, MyReturnObject>(new MyObject());
//Do something with the result MyReturnObject that you get back
}
}
public class ViewModel_Values : MvxViewModel<MyObject, MyReturnObject>
{
private readonly IMvxNavigationService _navigationService;
public ViewModel_Values(IMvxNavigationService navigation)
{
_navigationService = navigationService;
}
public override void Prepare(MyObject parameter)
{
//Do anything before navigating to the view
//Save the parameter to a property if you want to use it later
}
public override async Task Initialize()
{
//Do heavy work and data loading here
}
public async Task SomeMethodToClose()
{
// here you returned the value
await _navigationService.Close(this, new MyReturnObject());
}
}
More info here
HIH
Use messaging center. Here is the sample code.
//for trigger
MessagingCenter.Send<object> (this, "Hi");
//put this where you want to receive your data
MessagingCenter.Subscribe<object> (this, "Hi", (sender) => {
// do something whenever the "Hi" message is sent
});
Installing & using the MvxMessenger plugin is a great way to decouple view model communication in MvvmCross -
In your case, you could set up a new message -
public class ValuesChangedMessage : MvxMessage
{
public ValuesChangedMessage(object sender, int valuea, string valueb)
: base(sender)
{
Valuea = valuea;
Valueb = valueb;
}
public int Valuea { get; private set; }
public string Valueb { get; private set; }
}
In ViewModel_Values, you would act on / publish your UX changes with -
_mvxMessenger.Publish<ValuesChangedMessage>(new ValuesChangedMessage(this, 1, "boo!"));
And in ViewModel_A, ViewModel_B you would subscribe and act on them (as your ViewModel A / B would be still in the navigation stack when you pushed ViewModel_Values from them, so they could receive the message) -
private MvxSubscriptionToken _messageToken;
_messageToken = _mvxMessenger.Subscribe<ValuesChangedMessage>(async message =>
{
// use message.Valuea etc ..
});
More infos here -
https://www.mvvmcross.com/documentation/plugins/messenger?scroll=644
https://www.youtube.com/watch?feature=player_embedded&v=HQdvrWWzkIk
In my case of trying to navigate in this pattern:
//pseudo code
"ModelA" => "ModelB<List<MyObject>>" => "ModelC<MyObject>"
OR
//pseudo code
"ModelA" => "ModelC<MyObject>"
I used the following work around in my ViewDestroy() override of ModelB<List>:
private bool destroyView = true;
public bool DestroyView
{
get => destroyView;
set
{
destroyView = value;
RaisePropertyChanged(() => DestroyView);
}
}
public override void ViewDestroy(bool viewFinishing)
{
viewFinishing = DestroyView;
base.ViewDestroy(viewFinishing);
}
private async Task ModifySelectedObject()
{
DestroyView = false;
MyObject obj = SelectedObject;
MyObject modifiedObj = await _navigationService.Navigate<ModifySingleViewModel, MyObject, MyObject>(new MyObject());
if (modifiedObj != null)
{
obj = modifiedObj;
}
else
{
await Application.Current.MainPage.DisplayAlert("", "No changes made.", "OK");
}
DestroyView = true;
}
This keeps the original
"await _navigationService.Navigate<ModifyMultipleViewModel,
List, List>(new MyObject);"
from ModelA open when navigating to ModelC from ModelB, but still allows the ViewDestroy Method to close otherwise.

How do I change the values of variables within one class, from another?

Please excuse my ignorance for I am new to C#.
I am currently working on an MVVM project in which a viewmodel has multiple instantiated public variables that are data-bound to elements in a view (WPF). When these variables are changed they automatically update in my view. Take for instance the code segment below from my view model...
private string _displaybind;
public string DisplayBind
{
get { return _displaybind; }
set
{
SetProperty(ref _displaybind, value);
if (_displaybind.Length > 5000)
{
DisplayBind = _displaybind.Substring(_displaybind.IndexOf('\n') + 1);
}
}
}
By using the command DisplayBind = "Hello"; within my viewmodel I can push out text to a textbox I have located in my XAML view. Unfortunately, I have reached a point where I can not simply edit the value of DisplayBind.
I need to start a state machine within my viewmodel which will access several states (classes) in separate C# files. However, I have no idea how to receive, and more importantly edit the values within my viewmodel from these separate classes.
I start my state machine in my viewmodel using this...
IPMProgram ipmprogram = new IPMProgram();
ipmprogram.StartTheIPMProgram();
This is my IPMProgram class
public class IPMProgram
{
public IPMProgramState currentState = null;
public IPMProgram()
{
currentState = new BootBannerState(this);
}
public void StartTheIPMProgram()
{
while (true)
{
currentState.GetNextState();
}
}
}
This is my IPMProgramState class
public abstract class IPMProgramState
{
private IPMProgram ipmprogram;
public IPMProgram Ipmprogram
{
get { return ipmprogram; }
set { ipmprogram = value; }
}
public abstract void GetNextState();
}
And this is my BootBannerState class (The state I want to edit DisplayBind from)
class BootBannerState : IPMProgramState
{
public BootBannerState(IPMProgramState state)
:this(state.Ipmprogram)
{
}
public BootBannerState(IPMProgram ipmprograminstance)
{
this.Ipmprogram = ipmprograminstance;
}
public override void GetNextState()
{
//DisplayBind = "Hello"!
return;
}
}
Someone suggested that I should look into Dependency Injection, but I don't quite understand how it would work for me. What should I do?
Thank you for all of your help,
Tesnich

share observableCollection between ViewModels

I have a ViewModel that holds an ObservableCollection. This collection is getting updated all the time. Now I want to share this ObservableCollection with another ViewModel.
VM1
public Class VM1
{
private ObservableCollection<CameraPackage> _cameraPackagesPerScenes = new ObservableCollection<CameraPackage>();
public ObservableCollection<CameraPackage> CameraPackagesPerScenes
{
get { return _cameraPackagesPerScenes; }
set { _cameraPackagesPerScenes = value; RaisePropertyChanged(); }
}
public VM1
{
var SCFunctions = new SharedCollectionLayer();
SCFunctions.AddToCameraPackagesPerSceneAndPartials(CameraPackagesPerScene);
}
}
Now I want to use the same ObservableCollection in VM2. How do I do this?
I already tried to create a new class that holds the ObservableCollection I achieve to get the data in the Collection but When I try to get it from the other VM it becomes null.
public class SharedCollectionLayer
{
private ObservableCollection<CameraPackage> _cameraPackagesPerScenes = new ObservableCollection<CameraPackage>();
public ObservableCollection<CameraPackage> CameraPackagesPerScenes
get { return _cameraPackagesPerScenes ; }
set { _cameraPackagesPerScenes = value; RaisePropertyChanged(); }
}
}
public ObservableCollection<CameraPackage> GetCameraPackagesPerSceneAndPartials()
{
var CameraPackagesPerScene = new ObservableCollection<CameraPackage>();
foreach (CameraPackage camerapackage in CameraPackagesPerSceneAndPartials)
{
CameraPackagesPerScene.Add(camerapackage);
}
return CameraPackagesPerScene;
}
public void AddToCameraPackages(ObservableCollection<CameraPackage> CameraPackagesPerScene)
{
foreach(CameraPackage camerapackage in CameraPackagesPerScene)
{
CameraPackagesPerSceneAndPartials.Add(camerapackage);
}
}
}
And in VM2 I'm trying to get the items in the observableCollection but it returns empty.
VM2
public Class VM2
{
private ObservableCollection<CameraPackage> _cameraPackagesPerScene = new ObservableCollection<CameraPackage>();
public ObservableCollection<CameraPackage> CameraPackagesPerScene
{
get { return _cameraPackagesPerScene; }
set { _cameraPackagesPerScene = value; RaisePropertyChanged(); }
}
public VM
{
var SCFunctions = new SharedCollectionLayer();
CameraPackagesPerScene = SCFunctions.GetCameraPackagesPerSceneAndPartials();
}
}
I'm very new to WPF and MVVM, so What is the right approach of sharing ObservableCollections between ViewModels?
Based on the information, here is what is wrong
In both of your view models you are creating new objects of SharedCollectionLayer. So both of them have different instances and hence you are not getting the values from VM1 to VM2
Solution
The way to solve this problem is to have a single object shared between both of the view models. You can either follow a singleton pattern below, or a factory pattern where the factory decides what model to give to a view model
Make your SharedCollectionLayer a singleton and then reference it in both the view models.
So you would have something like this
public class SharedCollectionLayerSingleton
{
private static SharedCollectionLayerSingleton instance
public static SharedCollectionLayerSingleton Instance
{
if(instance == null)
{
instance = new SharedCollectionLayerSingleton()
}
return instance;
}
private SharedCollectionLayerSingleton()
{
// initialize here
}
}
In the factory method, you can basically have a factory create a model or return the same model based on certain conditions. Do not have view models as the conditions but state of your application as the condition.
I do not know how your collection is changing so I cannot really comment what is a better way. Regardless of which pattern you follow make sure to keep the observable collection in the same object as long as you want views of both viewmodels reflecting the change simultaneously.
Hope this helps

Categories