I'm newbie in MVVM design pattern, and I have these viewmodels :
ClassAViewModel
public class ClassAViewModel : INotifyPropertyChanged
{
private int _nbre = 0;
public int Nbre
{
get
{
return _nbre;
}
set
{
_nbre = value;
PropertyChanged(this, new PropertyChangedEventArgs("Nbre"));
}
}
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
And ClassBViewModel
PUBLIC class ClassBViewModel: INotifyPropertyChanged
{
private Boolean _IsBiggerthanFive = false;
public bool IsBiggerthanFive
{
get
{
return _IsBiggerthanFive;
}
set
{
_IsBiggerthanFive = value;
PropertyChanged(this, new PropertyChangedEventArgs("IsBiggerthanFive"));
}
}
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I need to know if a mecanism of notification between two viewmodels exists , ie in my case if _nbre > 5 in the first viewmodel, the second viewmodel will be notified and the value of _IsBiggerthanFive will be changed. So:
How can two viewmodels communicate between them without instanciate one in the other ?
What is the best way to accomplish this task?
I agree with other commenters that the mediator/pub-sub/event aggregator/messenger is a good way to go. If you're not using an MVVM framework with a built-in solution, then I recommend this simple approach that takes advantage of the Reactive extensions:
public class EventPublisher : IEventPublisher
{
private readonly ConcurrentDictionary<Type, object> subjects
= new ConcurrentDictionary<Type, object>();
public IObservable<TEvent> GetEvent<TEvent>()
{
var subject =
(ISubject<TEvent>) subjects.GetOrAdd(typeof (TEvent),
t => new Subject<TEvent>());
return subject.AsObservable();
}
public void Publish<TEvent>(TEvent sampleEvent)
{
object subject;
if (subjects.TryGetValue(typeof(TEvent), out subject))
{
((ISubject<TEvent>)subject)
.OnNext(sampleEvent);
}
}
}
That's your whole event aggregator. Pass an instance of it into each view model, and store it as a reference. Then create a class to store your event details, let's say "ValueChangedEvent":
public class ValueChangedEvent
{
public int Value
{
get { return _value; }
}
private readonly int _value;
public ValueChangedEvent(int value)
{
_value = value;
}
}
Publish like this from the first view model:
set
{
_nbre = value;
PropertyChanged(this, new PropertyChangedEventArgs("Nbre"));
_eventPublisher.Publish(new ValueChangedEvent(value));
}
Subscribe in the other class using GetEvent:
public class ClassBViewModel: INotifyPropertyChanged, IDisposable
{
private readonly IDisposable _subscriber;
public ClassBViewModel(IEventPublisher eventPublisher)
{
_subscriber = eventPublisher.Subscribe<ValueChangedEvent>(next =>
{
IsBiggerthanFive = next.Value > 5;
});
}
public void Dispose()
{
_subscriber.Dispose();
}
}
A messenger service is a solution. MVVM Light Toolkit has an implementation of this. What you can do with it, is listen to a specific type of message in your viewmodel and handle it through the messenger. http://www.mvvmlight.net/
Related
When I searched,I found,how to bind values from viewmodel to view but not viewmodel to viewmodel.can anyone help me to do that. what i need is to pass Authentication to other viewmodel.I am new in the MVVM world so please give me more detail.
my ViewModel look like this
public class ModelView_Authentication : INotifyPropertyChanged
{
//Binding authentication
private Authentication _authentication;
public Authentication authentication
{
get { return _authentication; }
set
{
_authentication = value;
NotifayPropertyChanged("_authentication");
}
}
//Command Button
public ModelView_Authentication()
{
authentication = new Authentication();
ButtonCommand = new ViewModdelCommand(exeMethode, canexeMethode);
}
public ICommand ButtonCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private bool canexeMethode(Object param)
{
return true;
}
//run this Command Onclick Button
private void exeMethode(Object param)
{
}
protected void NotifayPropertyChanged(string s)
{
PropertyChangedEventHandler pc = PropertyChanged;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(s));
}
}
//Run Assync Login
public static async Task<string> main(Authentication authentication)
{
var tocken = await Login.GetConnection(authentication);
return tocken.ToString();
}
}
need is to pass Authentication to other viewmodel
Your main ViewModel adheres to INotifyPropertyChanged, you can have your other VMs subscribe to the notification process of the main VM and acquire changes to specific properties as needed.
Just have a reference to the main VM, it is as easy as that. Where the VMs get their references, that process is up to you.
A good place is on App class. Since the App class is known throughout each of the namespaces, setup up a static property on it, set it after the main VM is created, and then access the it as needed.
public static ModelView_Authentication AuthVM { get; set; }
the access such as
var mainVM = App.AuthVM;
I have trouble with the ViewModel of my MVVM pattern-code.
I have a bunch of measurements and a bunch of rules to evaluate the measurements, stored in the classes Rule and Measurement. In my main class MyClass I store my Rules and Measurements then in ObservableCollections (OC) (and connected to a DataGrid).
For all n Rules I create n CollcetionOfEvaluators in one OC and pass the respective rule and all the measurements to each single one.
In each CollectionOfEvaulators I create for the one rule and the m Measurements m Evaluators in an OC.
The Evaluators take the one Rule and the one Measurement and gives back a bool if or if not the respective Measurement passes the respective Rule.
I then have a ListView that displays for each Rule a DataGrid that shows for every Measurement if it passed the Rule.
My problem is to make the Evaluator class to fire the OnPropertyChanged method, if I change the properties of one of the measurements in MyClass. How can I pass the info basically from one child to another child's child? When I play around with the DataGrid of the Evaluators, for example click on the header to rearrange it, it works. So I guess the problem is the c# code not the xaml. So I will leave it out for once. All the bindings are Mode=TwoWay (except the bool, since it has no setter) and UpdateSourceTrigger=PropertyChanged.
I tried to sketch the problem:
This is my code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Text;
namespace demo
{
public class MyClass : INotifyPropertyChanged
{
public class Measurement : INotifyPropertyChanged
{
private double? myValue1;
public double? MyValue1
{
get { return myValue1; }
set
{
myValue1 = value;
OnPropertyChanged("MyValue1");
}
}
private double? myValue2;
public double? MyValue2
{
get { return myValue2; }
set
{
myValue2 = value;
OnPropertyChanged("MyValue2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class EvaluationRule
{
public EvaluationRule(double Value1Min, double Value2Min)
{
this.Value1Min = Value1Min;
this.Value2Min = Value2Min;
}
public double Value1Min;
public double Value2Min;
}
public class Evaluator : INotifyPropertyChanged
{
public Evaluator(Measurement Measurement, EvaluationRule Rule)
{
this.Rule = Rule;
this.Measurement = Measurement;
}
public EvaluationRule Rule;
private Measurement measurement;
public Measurement Measurement
{
get { return measurement; }
set
{
measurement = value;
OnPropertyChanged("Measurement");
}
}
public bool IsApproved
{
get
{
if (measurement.MyValue1 > Rule.Value1Min
&& measurement.MyValue2 > Rule.Value2Min)
{
return true;
}
return false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CollectionOfEvaluators : INotifyPropertyChanged
{
public CollectionOfEvaluators(EvaluationRule Rule, ObservableCollection<Measurement> Measurements)
{
this.Rule = Rule;
this.Measurements = Measurements;
var Evaluators = new ObservableCollection<Evaluator>();
foreach (var _measurement in Measurements)
{
var _evaluator = new Evaluator(_measurement, this.Rule);
Evaluators.Add(_evaluator);
}
}
public EvaluationRule Rule;
private ObservableCollection<Measurement> measurements;
public ObservableCollection<Measurement> Measurements
{
get { return measurements; }
set
{
measurements = value;
OnPropertyChanged("Measurements");
}
}
private ObservableCollection<Evaluator> evaluators;
public ObservableCollection<Evaluator> Evaluators
{
get { return evaluators; }
set
{
evaluators = value;
OnPropertyChanged("Evaluators");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<Measurement> measurements;
public ObservableCollection<Measurement> Measurements
{
get { return measurements; }
set
{
measurements = value;
OnPropertyChanged("Measurements");
}
}
private ObservableCollection<EvaluationRule> rules;
public ObservableCollection<EvaluationRule> Rules
{
get { return rules; }
set
{
rules = value;
GetCollection();
}
}
private ObservableCollection<CollectionOfEvaluators> collection;
public ObservableCollection<CollectionOfEvaluators> Collection
{
get { return collection; }
set
{
collection = value;
OnPropertyChanged("Collection");
}
}
public void GetCollection()
{
var Collection = new ObservableCollection<CollectionOfEvaluators>();
foreach (var _rule in rules)
{
var _collection = new CollectionOfEvaluators(_rule, Measurements);
Collection.Add(_collection);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
You must delegate the event. Evaluator should listen to the PropertyChanged event of its aggregated Measurement. The handler of this event can then raise the Evaluator.PropertyChanged event in response:
public class Evaluator : INotifyPropertyChanged
{
public Evaluator(Measurement measurement, EvaluationRule rule)
{
this.Rule = rule;
this.Measurement = measurement;
this.Measurement.PropertyChanged += OnMeasurementPropertyChanged;
}
public void OnMeasurementPropertyChanged(object sender, PropertyChangedEventAgrs e)
{
OnPropertyChanged(nameof(this.Measurement));
}
private Measurement measurement;
public Measurement Measurement
{
get => this.measurement
set
{
this.measurement = value;
OnPropertyChanged(nameof(this.Measurement));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Note that you got a spelling error when naming your class. It's Measurement - you missed an 'a'. Also parameter names should always be lowercase.
So, I have a statusbar as UserControl.
Model:
public class StatusBarModel : BindableBase
{
private string _status;
public string Status
{
get { return _status; }
set
{
_status = value;
RaisePropertyChanged("Status");
}
}
private int _p_value;
public int P_Value
{
get { return _p_value; }
set
{
_p_value = value;
RaisePropertyChanged("P_Value");
}
}
}
ViewModel:
public class StatusBarVM : BindableBase
{
readonly source.elements.StatusBar.StatusBarModel _model = new source.elements.StatusBar.StatusBarModel();
public StatusBarVM()
{
_model.PropertyChanged += (s, e) => { RaisePropertyChanged(e.PropertyName); };
}
public string Status
{
get { return _model.Status; }
set { _model.Status = value; }
}
public int P_Value
{
get { return _model.P_Value; }
set { _model.P_Value = value; }
}
}
And for example I wanna change Status variable from others ViewModels.
How I can do it? I have seen examples with only buttons and etc.
There are multiple ways to achieve your requirement. as #bitclicker says, you can use static class that hold its value. But I think It is too much that makes it static class, because that variable value may be used only two viewmodel.
I suggest you communicate between two view model. you will find Prism's event aggregator or you could implement your own event publish-subscriber model. making your own event pub-sub model would help you to make a first step into the design pattern.
You could create a static class to hold that value.
public static class Globals()
{
public static StatusBarModel GlobalStatus { get; set; }
}
Then whenever you want to alter it you just do
Globals.GlobalStatus.Status = "something";
Globals.GlobalStatus.P_Value = 14;
does that accomplish what you need?
I'm working through the following tutorial http://www.mindscapehq.com/blog/index.php/2012/2/1/caliburn-micro-part-4-the-event-aggregator/
and I'm currently stuck at the publish / subscribe part.
I have everything set up, so that it should actually publish the events but the subscribing viewmodel doesn't get the message.
I've done the following:
Publishing ViewModel:
[Export(typeof(ColorViewModel))]
public class ColorViewModel : PropertyChangedBase
{
private readonly IEventAggregator events;
[ImportingConstructor]
public ColorViewModel(IEventAggregator events)
{
this.events = events;
}
public void Red()
{
this.events.PublishOnUIThread(new ColorEvent(new SolidColorBrush(Colors.Red)));
}
public void Green()
{
this.events.PublishOnUIThread(new ColorEvent(new SolidColorBrush(Colors.Green)));
}
public void Blue()
{
this.events.PublishOnUIThread(new ColorEvent(new SolidColorBrush(Colors.Blue)));
}
}
Subscribing ViewModel:
[Export(typeof(AppViewModel))]
public class AppViewModel : PropertyChangedBase, IAppViewModel, IHandle<ColorEvent>
{
private IEventAggregator events;
[ImportingConstructor]
public AppViewModel(ColorViewModel colorViewModel, IEventAggregator events)
{
this.ColorViewModel = colorViewModel;
this.events = events;
this.events.Subscribe(this);
}
public ColorViewModel ColorViewModel { get; private set; }
private SolidColorBrush color;
public SolidColorBrush Color
{
get
{
return this.color;
}
set
{
this.color = value;
this.NotifyOfPropertyChange(() => this.Color);
}
}
public void Handle(ColorEvent message)
{
this.Color = message.Color;
}
}
There are 3 radio buttons on the ColorView which I can click and I do get into the Red(), Green(), Blue() methods so that the PublishOnUIThread is called.
But I never reach the Handle(ColorEvent) method of the AppViewModel.
Am I missing something or why doesn't my handle method gets called after publishing the ColorEvents?
Thanks in advance
Where is the event aggregator coming from? Is it the same instance shared between AppViewModel and ColorViewModel?
Make sure the event aggregator is registered as a singleton in the dependency injector.
Maybe I don't understand the ObservableCollection well enough. But as far as I knew it was similar to a normal list, but with event triggers so that you can react to changes.
So I have this Windows store app. And in this application I have a main BusinessModel class which is the main source for all data in my client application. This data will be updated when the server has made some changes elsewhere. In the future I'd like to have this class update the ViewModels for specific data updates etc.
So I also have a ViewModel class which contains, at least in my PoC's so far, a copy of that list (also in the near future this list will have an enriched version of the list).
Since it's a copy they should be both separate instances and have their own separate items.
However when I update the copy in the ViewModel, the BusinessModel version changes with it.
And vice versa.
I can't seem to figure out why this is happening. Underneath you will find the classes and their functions:
//the BusinessModel Class
public class ModelStuff : INotifyPropertyChanged
{
private ObservableCollection<DataObject> _modelStuff;
public ObservableCollection<DataObject> modelStuff
{
get
{
return _modelStuff;
}
set
{
_modelStuff = value;
NotifyPropertyChanged("modelStuff");
}
}
private static ModelStuff businessModel;
public static ModelStuff BusinessModel
{
get
{
if (businessModel == null)
{
businessModel = new ModelStuff();
}
return businessModel;
}
}
public ModelStuff()
{
modelStuff = new ObservableCollection<DataObject>();
modelStuff.Add(new DataObject(0));
modelStuff.Add(new DataObject(1));
modelStuff.Add(new DataObject(2));
modelStuff.Add(new DataObject(3));
modelStuff.Add(new DataObject(4));
modelStuff.Add(new DataObject(5));
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
//the ViewModel class
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<DataObject> _visibleStuff;
public ObservableCollection<DataObject> visibleStuff
{
get
{
return _visibleStuff;
}
set
{
_visibleStuff = value;
NotifyPropertyChanged("visibleStuff");
}
}
private static ViewModel tvm;
public static ViewModel TVM
{
get
{
if (tvm == null)
{
tvm = new ViewModel();
}
return tvm;
}
}
public ViewModel()
{
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber));
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
}
//the TestObjects
public class DataObject
{
public int testNumber { get; set; }
public String testStr { get; set; }
public DataObject(int i)
{
testNumber = i;
testStr = "testje";
}
}
//A randomly placed button invokes this function when clicked.
private void Button_Click(object sender, RoutedEventArgs e)
{
//do stuff here
int i0 = ModelStuff.BusinessModel.modelStuff[0].testNumber;
ViewModel.TVM.visibleStuff[0].testNumber = 100;
int i1 = ModelStuff.BusinessModel.modelStuff[0].testNumber;
//i1 has the value 100 in my logs! :S
}
//Second version but vice versa
private void Button_Click(object sender, RoutedEventArgs e)
{
//do stuff here
int i0 = ViewModel.TVM.visibleStuff[0].testNumber;
ModelStuff.BusinessModel.modelStuff[0].testNumber = 100;
int i1 = ViewModel.TVM.visibleStuff[0].testNumber;
//i1 has the value 100 in my logs! :S
}
Where has my reasoning gone wrong?
Why is this happening?
And more importantly, how can I prevent this behaviour?
As far as I can see, your line of code:
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber));
is not making a copy of the underlying objects at all. It is adding the same DataObjects from the original list to a new ObservableCollection.
You need to clone the DataObjects individually and add them to the new collection. Something like this should do it:
visibleStuff = new ObservableCollection<DataObject>(ModelStuff.BusinessModel.modelStuff.OrderBy(c => c.testNumber).Select(i => new DataObject(i.testNumber)));