Both ViewModels know nothing about each other, but i need to send the new value that have changed in one viewmodel to a method in the other view model, what are my options?
could you please list all possibilities and what would be the best way?
Taken from this answer:
If you want loosely-coupled communication you need an EventAggregator:
//Simplest EventAggregator
public static class DumbAggregator
{
public static void BroadCast(string message)
{
if (OnMessageTransmitted != null)
OnMessageTransmitted(message);
}
public static Action<string> OnMessageTransmitted;
}
Usage:
public class MySender
{
public void SendMessage()
{
DumbAggregator.BroadCast("Hello There!");
}
}
public class MySubscriber
{
public MySubscriber()
{
DumbAggregator.OnMessageTransmitted += OnMessageReceived;
}
private void OnMessageReceived(string message)
{
MessageBox.Show("I Received a Message! - " + message);
}
}
Notice however, that EventAggregators included in MVVM frameworks such as Prism are much more complex and include a whole lot of functionality. This is just a simple example.
Related
I have a class which has some events. At present, I decide to shift towards "Observables" since the benefits they propose. To reach this goal, I introduced an Observable property to be replaced with one of the events. Next, I made the event "private" to restrict its accessibility from out of the class. The event is invoked inside the class with a function when it is needed. However, I think It may be a better way to do this job. What is the proper practice? By the way, I am a novice in "System.Reactive," so if I have a misunderstanding with the concept, please clarify the matter. My code is below:
public class MyClass
{
public MyClass()
{
InformationSenderObservable=Observable.FromEventPattern<SolutionEventArg>(ev =>InformationSender += ev, ev => InformationSender -= ev);
}
private event EventHandler<SolutionEventArg> InformationSender;
public IObservable<EventPattern<SolutionEventArg>> InformationSenderObservable { get; }
internal void DoSomething()
{
// long calculation
SendInformation();
}
private void SendInformation()
{
InformationSender?.Invoke(this,new SolutionEventArg()
{
InfoProxyData = GetDetailsForBestCompressedData(),
ParallelProcess = parallelProcessing
});
}
}
Thanks to Theodor Zoulias, I finally understood how I could better handle my class. I used ISubject<T> instead of events in my class to send information from my class to another class subscribing to the ISubject<T>.
public class MyClass
{
public MyClass()
{
InformationSenderObservable=new Subject<SolutionEventArg>();
}
public ISubject<SolutionEventArg> InformationSenderObservable { get; }
internal void DoSomething()
{
// long calculation
SendInformation();
}
private void SendInformation()
{
try
{
InformationSenderObservable.OnNext(new SolutionEventArg()
{
InfoProxyData = GetDetailsForBestCompressedData(),
ParallelProcess = parallelProcessing
});
}
catch (Exception e)
{
InformationSenderObservable.OnError(e);
}
}
}
ISubject<T> implements both IObservable<T> and IObserver<T> simultaneously.
The Structure
I have a simple form that fires off a timer that checks for updates pretty regularly. The constructor of the form that starts on load looks like so:
public MainWindow()
{
InitializeComponent();
otherWindow = new TheOtherWindow();
if (Meta.hasUpdate)
{
updateImage.Source = new BitmapImage(new Uri("/MyProject;component/Images/updateTrue.gif", UriKind.Relative));
}
Thread updateMonitor = new Thread(() =>
{
UpdateManager updater = new UpdateManager();
updater.StartUpdateMonitor();
});
updateMonitor.IsBackground = true;
updateMonitor.Start();
}
The Meta class contains some very basic information, storing various strings that are referenced in several places but are sometimes updated. Among that structure is this:
class Meta
{
...
private static bool hasUpdate = false;
public static bool GetHasUpdate()
{
return hasUpdate;
}
public static void SetHasUpdate(bool value)
{
hasUpdate = value;
}
}
The other piece is the UpdateManager class, which includes this a small routine to check for an update every 5 minutes.
class UpdateManager
{
Timer timer;
public void CheckForUpdates(Object source, ElapsedEventArgs e)
{
if (!isUpToDate())
{
timer.Stop();
Meta.SetHasUpdate(true);
Application.Current.Dispatcher.Invoke(new Action(() =>
{
MessageBox.Show("A new update is now available!);
}));
}
}
public void StartUpdateMonitor()
{
float updateInterval = 300000;
timer = new Timer(updateInterval); // Milliseconds between checks.
timer.Elapsed += CheckForUpdates;
timer.AutoReset = true;
timer.Enabled = true;
}
}
The Problem
In short, I want to fire off an event whenever Meta.SetHasUpdate() is reached that then broadcasts this to all the forms in the application with the goal of changing a small icon to indicate that an update is available.
My attempts to do so have ended with me learning that implementing INotifyPropertyChanged does not play nice with Static members. This was my attempt in implementing that...
class Meta : INotifyPropertyChanged
{
...
private static bool hasUpdate = true;
public static bool GetHasUpdate()
{
return hasUpdate;
}
public static void SetHasUpdate(bool value)
{
hasUpdate = value;
NotifyPropertyChanged();
}
private static void NotifyPropertyChanged()
{
if (PropertyChanged != null)
{
PropertyChanged(null, new PropertyChangedEventArgs("hasUpdate"));
}
}
}
Since these members need to be read back from multiple forms, I can't make them not static without passing an object around a lot, which I don't want to do.
How do fire off an event that multiple forms can receive from the Meta class in this case? Do I need to consider a different structure, or am I misunderstanding INotifyPropertyChanged?
While there can be many ways to solve this, (think DI of your Meta class into each of your pages' ViewModels and react to INPC..that would be preferred over singleton approach), one approach to consider is using Messaging rather than Events. Messages, (offered in most MVVM frameworks), are great way to communicate between loosely coupled components. If you leverage an MVVM Library like MVVM Light, then this is very easy as it includes a Messenger implementation. The main advantage of this approach is that the forms that you want to receive the notification don't necessarily need to hold on to a reference of the source, like you would with an Event based approach.
Simply have all interested forms register for a message, and react accordingly when received.
For example, with MVVM Light, we can take advantage of automatically broadcasting a message when a INPC property has been updated.
private bool hasUpdate;
public bool HasUpdate
{
{
return hasUpdate;
}
set
{
// the last bool param indicates whether or not to broadcast a message to all interested parties.
Set(nameof(HasUpdate), ref hasUpdate, value, true);
}
}
Then in a totally separate / unrelated part of the app, (usually in a ViewModel), we can do this to indicate that we are interested in such an update:
Messenger.Default.Register<PropertyChangedMessage<bool>>(this, m => ReceiveHasUpdatedMessage(m));
and then in the receiving lambda:
private void ReceiveHasUpdatedMessage(PropertyChangedMessage<bool> m)
{
// react accordingly.
}
This is just one simple use case of the Messenger that MVVM Light provides.. you can do pretty much anything you want. The premise here is that using this approach decouples interested parties from requiring a hard reference to the emitter.
With a combination of everyone's very helpful advice, I've put together the following code. The MVVM solution is above, although I did not test it. If you aren't using MVVM though, this is what I did.
The UpdateManager class is the same. Meta has the following structure:
class Meta
{
private static bool hasUpdate = false;
public static event PropertyChangedEventHandler StaticPropertyChanged;
public static bool GetHasUpdate()
{
return hasUpdate;
}
public static void SetHasUpdate(bool value)
{
hasUpdate = value;
StaticNotifyPropertyChanged();
}
private static void StaticNotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}
}
Then, for any form I want to be aware of this kind of a change, I bolt in the following code:
public partial class SomeForm : Window
{
public SomeForm()
{
InitializeComponent();
Meta.StaticPropertyChanged += MethodThatTriggersOnUpdate;
...
}
private void MethodThatTriggersOnUpdate(object sender, EventArgs e)
{
myImage.Dispatcher.BeginInvoke(
(Action)(() => myImage.Source = new BitmapImage(
new Uri("/MyProject;component/Images/myNewImage.gif", UriKind.Relative))));
}
...
}
first of all, this may be stupid/dumb question but anyway, this is not a problem, basically im searching for the best proper way to show a result from a class to the main window.
now i will show you what i need, and then how i would solved this with my current knowledge, knowing that its incorrect way,
so on button click i do this:
private void btnSend_Click(object sender, RoutedEventArgs e)
{
new Notification().DoNotify(txtMessage.Text);
}
the classes definitions are:
public class Notification
{
private IMessenger _iMessenger;
public Notification()
{
_iMessenger = new Email();
}
public void DoNotify(string Message)
{
_iMessenger.SendMessage(Message);
}
}
interface IMessenger
{
void SendMessage(string Message);
}
public class SMS : IMessenger
{
public void SendMessage(string Message)
{
// i want code that will print this message variable to the txtSMS textbox.
}
}
public class Email : IMessenger
{
public void SendMessage(string Message)
{
// i want code that will print this message variable to the txtEmail textbox.
}
}
now how to update the main window GUI from these classes?,
one solution that i would use is, add new tempclass with the mainwindow object inside, and then access it,
public class TempClass
{
public static MainWindow Main;
}
in main window constructor:
public MainWindow()
{
InitializeComponent();
TempClass.Main = this;
}
and the sms/email classes:
public class Email : IMessenger
{
public void SendMessage(string Message)
{
TempClass.Main.txtEmail.Text = Message;
}
}
public class SMS : IMessenger
{
public void SendMessage(string Message)
{
TempClass.Main.txtSMS.Text = Message;
}
}
now this works, but there just has to be a better way... what do you think and sorry for long post... ://
is there any principle or design pattern about this ?
A static property is not recommended when you might have two or more instances of your application. You can define a method in MainWindow:
internal void SetMessage(string Message)
{
Dispatcher.BeginInvoke(new Action(() => {txtSMS.Text = Message;}));
}
and send the instance of your MainWindow to other classes, whether by a specific constructor
MainWindow parentWindow;
void btnSend_Click(object sender, RoutedEventArgs e)
{
if (parentWindow != null)
_parentWindow.SetMessage(txtMessage.Text);
}
or by defining a DependencyProperty and use Binding.
You can also use Events, however, I prefer the preceding.
I have an application where I open a new view (with viewmodel) within another viewmodel. The new Viewmodel needs some parameters from the caller viewmodel.
Now I introduced a class called Messenger which looks like:
public class Messenger
{
private static Messenger instance;
public static Messenger Instance
{
get { return instance ?? (instance = new Messenger()); }
}
private Messenger() { }
public void SendMessage(Message message)
{
OnMessageSent temp = MessageSent;
if (temp != null)
temp(message);
}
public delegate void OnMessageSent(Message message);
public event OnMessageSent MessageSent;
}
The message-class looks like:
public class Message
{
public Type TargetType { get; set; }
public object Sender { get; set; }
public Type ValueType { get; set; }
public object Value { get; set; }
public string AdditionalInfo { get; set; }
}
The usage of this building is:
The receiver-viewmodel of a message has to add
Messenger.Instance.MessageSent += MessageSent;
to it's constructor. And in the implementation can look like:
private void MessageSent(Message message)
{
if(message.TargetType == typeof(SecondViewModel))
{
if (message.ValueType == typeof (double))
{
this.MyValue= (double) message.Value;
}
}
}
This works fine. My question is: Is this a good way for the communication between viewmodels or are there any weakenings in this design?
You don't need to re-invent the wheel when its already there.
Use EventAggregator which internally uses weak event pattern to prevent any memory leaks. Refer to tutorials here and here.
Also you can use Event Aggregator with Reactive Extensions.
Refer to the post for viable approaches you can reuse - Even Aggregator Implementation Sample/Best Practices.
Here's an explanation of what I'm trying to achieve:
I have a textbox that I'm using as a 'debug', or 'information' window on my form. What I would like to do is have any classes that I create throw an event when it has information to post to the debug window, and then have the text window subscribe to said event, and post each time something new comes in. I'm trying to make it so that my classes don't need knowledge of the textbox but still have the capability to pass all of the information to the text box.
Is it possible to have a 'shared' event among classes (perhaps using an interface) so that I only need to subscribe to that one event and it will pull from all classes that throw the event?
For a visual, it would basically look like this:
Public delegate void DebugInfo(string content)
Class foo1
{
event DebugInfo DebugContentPending
public void bar()
{
DebugContentPending("send this info to the debug window")
}
}
Class foo2
{
event DebugInfo DebugContentPending
public void bar()
{
DebugContentPending("send this info to the debug window")
}
}
Class SomeClass
{
public void SomeMethod()
{
DebugContentPending += new DebugInfo(HandleContent); //gets events from foo1 and foo2
}
public void HandleContent(string content)
{
//handle messages
}
}
is this possible or am I off my rocker?
Most likely you don't need events.
class DebugLogger
{
public DebugLogger(TextBox textBox)
{
this.TextBox = textBox;
}
public TextBox TextBox { get; private set; }
public static DebugLogger Instance { get; set; }
public void Write(string text)
{
this.TextBox.Text += text;
}
}
Initialization:
DebugLogger.Instance = new DebugLogger(textBox1);
Usage:
DebugLogger.Instance.Write("foo");
Notice that code is not thread safe. See Automating the InvokeRequired code pattern and related for more information.