share observableCollection between ViewModels - c#

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

Related

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.

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

Collection of comboBoxes in WinForms

I am working on small winforms app. One of my forms contains few comboBoxes:
As I am trying to use MVP pattern in my project, so I decided to create View and Presenter for that form. The communicate via adequate interface.
ComboBox can be fully decribed (for my needs) with its DataSource (i.e. list os strings) and SelectedIndex. That' s why I created proper interface:
public interface IMyView
{
MyViewPresenter { set; }
IEnumerable<string> ComboBox1stDataSource { get; set; }
int ComboBox1SelectedIndex { get; set; }
IEnumerable<string> ComboBox2ndDataSource { get; set; }
int ComboBox2ndSelectedIndex { get; set; }
//for third comboBox it will be the same
}
I implemented that interface in my View class:
public partial class MaterialDatabasePropertiesForm : Form, IMaterialDatabasePropertiesView, IMyView
{
public MaterialDatabasePropertiesPresenter Presenter { private get; set; }
public IEnumerable<string> ComboBox1stDataSource
{
get { return comboBox1st.DataSource as List<string>; }
set { comboBox1st.DataSource = value; }
}
public int ComboBox1SelectedIndex
{
get { return comboBox1st.SelectedIndex; }
set { comboBox1st.SelectedIndex = value; }
}
public IEnumerable<string> ComboBox2ndDataSource
{
get { return comboBox2nd.DataSource as List<string>; }
set { comboBox2nd.DataSource = value; }
}
public int ComboBox2ndSelectedIndex
{
get { return comboBox2nd.SelectedIndex; }
set { comboBox2nd.SelectedIndex = value; }
}
}
When everything is set like above I use properties declared in Interface in my Presenter to change properties of comboBoxes in form.
Although it may seem like a good solution it isn' t enough for me. In my origial application I have 14 comboBoxes, and that number may change in future.
What I am trying to is making it more elastic. I was thinking about creatin some collection of comboBoxes in view, but I can' t figure it out.
My sample solutin is bad, as it doesn' t even compile:
private List<List<string>> collectionOfComboBoxesDataSources = new List<List<string>>()
{
ref comboBox1st.DataSource, // I get error:
ref comboBox2nd.DataSource, // "Cannot acces non-static field
ref comboBox3rd.DataSource // <comboBoxName> in static context"
};
//this property would be part of IMyView
public List<List<string>> CollectionOfComboBoxesDataSources
{
get { return collectionOfComboBoxesDataSources; }
set { collectionOfComboBoxesDataSources = value; }
}
What can I do to create collection (or something working similar) to acces my comboBoxes properties?
You could try to iterate through your form.
List<ComboBox> listOfCombobox = new List<ComboBox>();
foreach(var combobox in this.controls.OfType<ComboBox>())
{
listOfCombobox.Add(combobox);
}
If you're trying to do this.
Then you can access the list via index, so you can access your properties of each combobox.

Update the model from the view model

Update the model from the view model
I have read some post about the MVVM but I not sure if understand the
way that the view model is updating the model
Currently I have two text boxes in the UI which is bound to the XAML view and call to the view model when the event was raised .
when should be the place in the view model when I updating the model?
This is the view model
class ViewModel:INotifyPropertyChanged
{
private String _url;
private String _TemplateType;
public string URL
{
get { return _url; }
set
{
if (value != _url)
{
_url= value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType
{
get { return _TemplateType; }
set
{
if (value != _TemplateType)
{
_TemplateType= value;
OnPropertyChanged("URL");
}
}
}
The model
internal class DefineAddinModel
{
public string TemplateType { get; set; }
public String URL { get; set; }
}
The ViewModel usually acts as a wrapper around the Model and contains a reference to the Model which is can update either in response to commands or automatically in property setters.
UPDATE:
Here's an example of having the VM act as a wrapper around the Model. This may seem useless in your example but you will find in many cases the VM's getters/setters need to do some sort of transformation on the values rather than simply passing them through.
class ViewModel:INotifyPropertyChanged
{
private DefineAddinModel model;
public string URL
{
get { return model.URL; }
set
{
if (value != model.URL)
{
model.url = value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType
{
get { return model.TemplateType; }
set
{
if (value != model.TemplateType)
{
model.TemplateType = value;
OnPropertyChanged("TemplateType");
}
}
}
The better way to update your Model Is by using an event, its safer, so choose weather using a button click or lost focus, or whatever you want
void button_click(object sender,eventsarg e)
{
MyObj.URL = App.Locator.MyVM.MyDefineAddinModel.URL;// App.Locator because MVVMLight is tagged
MyObj.TemplateType = App.Locator.MyVM.MyDefineAddinModel.TemplateType ;
}
but personnaly i Use the following steps :
1- In your ViewModel create a CurrentItem object of type DefineAddinModel and without OnPropertyChanged then bind it to the View(UI) DataContext of the RootElement on the View )
2- for the model I use the INotifyPropertyChanged for each propery
3- after binding the datacontext of your root element to the CurrentItem of your ViewModel then bind just URL and TemplateType properties to your Controls, so any thing changes on the textbox will update CurrentItem properties
you can also chose the type of the binding (On LostFocus, or OnPropertyChanged)
You need to bind your TextBoxes to the two properties URL and TemplateType.
Try to use Commands (in the ViewModel)instead of events (in The CodeBehind) since you are in MVVM.
For updating the model : use a button with it's Command property bound to OnSave just like this example:
private String _url;
private String _TemplateType;
private DefineAddinModel _defineAddin;
public DefineAddinModel DefineAddin
{
get {return _defineAddin;}
set
{
_defineAddin = value;
OnPropertyChanged("DefineAddin");
}
}
public string URL
{
get { return _url; }
set
{
if (value != _url)
{
_url= value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType
{
get { return _TemplateType; }
set
{
if (value != _TemplateType)
{
_TemplateType= value;
OnPropertyChanged("URL");
}
}
}
public RelayCommand OnSaved
{
get;
set;
}
public ViewModel()
{
DefineAddin = new DefineAddinModel();
OnSaved = new RelayCommand(()=>
{
DefineAddin.URL = URL ;
DefineAddin.TemplateType = TemplateType;
});
Think about using third parties like MVVMLight it helps you a lot with MVVM and the helpers around it (Commands, Messenger, ViewModelLocator ...)
I think that the correct answer here is 'it depends'.
In most general cases, the advantage of actually using a ViewModel is also to track 'transient state', i.e. the state of an 'edit in progress' operation.
In this particular case, you would not push your changes directly to the Model every time a value is updated, instead you would do this via an 'Update' ICommand implementation that will collect all the data from the ViewModel and push it down to the Model.
This approach gives you many advantages:
The user of the view can change their mind as many times as they want, and only when they are happy will the Model actually get updated with their definitive choices
It greatly reduces the load on your persistence service, since only final changes are pushed through.
It allows you to do final validation on a complete set of values, rather than transient states, and hence reduces programming complexity and overhead.
It also makes your UI far more fluid since all the examples above are pushing updates on the UI Dispatcher, and avoids you having to cater for this via Tasks or other async approaches.
The backing model is never in an inconsistent state, since I would imagine that all values on one View/ViewModel are related, and only make sense when updated together using an ACID approach.
Here's an example of how I'd do it.
public class ViewModel:INotifyPropertyChanged {
private String _url;
private String _TemplateType;
public ViewModel(){
UpdateCommand = new DelegateCommand(OnExecuteUpdate, OnCanExecuteUpdate);
}
public bool OnCanExecuteUpdate(object param){
// insert logic here to return true when one can update
// or false when data is incomplete
}
public void OnExecuteUpdate(object param){
// insert logic here to update your model using data from the view model
}
public ICommand UpdateCommand { get; set;}
public string URL{
get { return _url; }
set {
if (value != _url) {
_url= value;
OnPropertyChanged("URL");
}
}
}
public string TemplateType {
get { return _TemplateType; }
set {
if (value != _TemplateType) {
_TemplateType= value;
OnPropertyChanged("TemplateType");
}
}
}
... etc.
}
public class DelegateCommand : ICommand {
Func<object, bool> canExecute;
Action<object> executeAction;
public DelegateCommand(Action<object> executeAction)
: this(executeAction, null) {}
public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecute) {
if (executeAction == null) {
throw new ArgumentNullException("executeAction");
}
this.executeAction = executeAction;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter) {
bool result = true;
Func<object, bool> canExecuteHandler = this.canExecute;
if (canExecuteHandler != null) {
result = canExecuteHandler(parameter);
}
return result;
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged() {
EventHandler handler = this.CanExecuteChanged;
if (handler != null) {
handler(this, new EventArgs());
}
}
public void Execute(object parameter) {
this.executeAction(parameter);
}
}

Triggering RaisePropertyChanged when Sub Property is changed - MvvmCross

I have a class structure setup in the below way - when the Model's Property is changed the RaisePropertyChanged event isn't called. Is there anyway around this or do I need to flatten out the Complex Property inside the ViewModel?
Class ViewModel
{
public Model model {
get { return _Service.GetModel();}
set { _Service.SetModel(); RaisePropertyChanged(() => Model);
}
}
class Model
{
public string A {get;set;}
}
Class Service
{
}
I don't think there's any easy way around it.
Either you can change model so it supports INotifyPropertyChanged - e.g.
class Model : MvxNotifyPropertyChanged
{
public string A {
get { return _a; }
set { _a = value; RaisePropertyChanged(() => A); }
}
}
I've used this first approach when I used a stackoverflow library - which had model entities like http://stacky.codeplex.com/SourceControl/latest#trunk/source/Stacky/Entities/Answer.cs
... or you can wrap your model with another INotifyPropertyChanged class:
class ModelWrapper : MvxNotifyPropertyChanged
{
private readonly Model _m;
public ModelWrapper(Model m)
{ _m = m; }
public string A {
get { return _m.A; }
set { _m.A = value; RaisePropertyChanged(() => A); }
}
}
I've used this approach when I don't have any control over the Model class - when it's been given to me.
... or as an extension of that approach, you can flatten the property down to the parent ViewModel:
class MyViewModel : MvxViewModel
{
private readonly Model _m;
public ModelWrapper(Model m)
{ _m = m; }
public string A {
get { return _m.A; }
set { _m.A = value; RaisePropertyChanged(() => A); }
}
}
I've used this approach only when there are just a couple of properties to worry about.
Overall... remember that the ViewModel is there to be a Model of the View - it's OK to copy property values to/from Model objects.

Categories