Sending Update Events to Multiple Forms from a different Class - WPF - c#

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))));
}
...
}

Related

How to update GUI from FileWatcher event with Task-based Asynchronous Pattern (TAP)

Recently I started learning C# by writing a simple application that watches directory and updates form based on lines written to file in that directory. At some point I was stuck with common InvalidOperationException while trying to update form element from FileWatcher event.
I've searched stackoverflow, and it seems that I should use Task-based Asynchronous Pattern (TAP) in such situations, but I can't figure out which method I should flag as async, and which to start as a Task. There are many related questions on stackoverflow, but none I've found cover all 3 aspects of my application:
Using FileWatcher
Updating Form element
Using TAP
So, what is the best practice to update Form elements from events, fired by FileWatcher if I want to use Task-based Asynchronous Pattern? Or should I use another pattern / another application structure?
Here is a simplified example of my app:
// Form
public partial class FormMain : Form, IDisplayInterface
{
private CoreClass coreClass;
public void SetSomeVaue(string value)
{
label.Text = value;
}
public FormMain()
{
coreClass = new CoreClass();
coreClass.StartFileWatcher();
}
private void FormMain_Shown(object sender, EventArgs e)
{
coreClass.DisplayInterface = this;
}
}
// Interface
interface IDisplayInterface
{
void SetSomeVaue(string value);
}
// CoreClass
class CoreClass
{
public IDisplayInterface DisplayInterface;
public void StartFileWatcher()
{
FileSystemWatcher watcher = new FileSystemWatcher("C:\Some\Folder")
{
NotifyFilter = NotifyFilters.Size
};
watcher.Changed += new FileSystemEventHandler(FileUpdated);
watcher.EnableRaisingEvents = true;
}
private void FileUpdated(object source, FileSystemEventArgs e)
{
ParseFile(Path.Combine("C:\Some\Folder", e.Name));
}
private void ParseFile(string File)
{
// foreach (var line in newFileLines)
ParseNewRecord(line);
}
private void ParseNewRecord(string line)
{
if (someCondition && DisplayInterface != null)
{
// This triggers Exception for accessing FormMain from a thread that did not create it
DisplayInterface.SetSomeValue(someValue);
}
}
}
UPDATE 21.07:
It looks that I got the wrong idea about using TAP everywhere, so I finally did it by invoking a delegate containing my SetSomeVaue method and it works correctly (I hope that is a correct decision).
Thanks for response!

BindingList.Add() doesn't work cross thread even with lock

I am just learn C#/.NET and I met this problem.
So in my solution i have 2 projects: winforms UI and dll with logic. In dll i have BindingList which provides datasource for listBox in UI.
UI:
public partial class Form1 : Form
{
private Class1 _class1;
public Form1()
{
InitializeComponent();
_class1 = new Class1(); // logic class insatce
listBox1.DataSource = _class1.BindingList;
}
private void button1_Click(object sender, EventArgs e)
{
_class1.Add();
}
private void button2_Click(object sender, EventArgs e)
{
_class1.Remove();
}
}
Logic class:
public class Class1
{
public BindingList<string> BindingList { get; set; } = new BindingList<string>() ;
public void Add()
{
var th = new Thread(() =>
{
lock (BindingList)
{
BindingList.Add("1");
}
}) {IsBackground = true};
th.Start();
// works fine
//BindingList.Add("1");
}
public void Remove()
{
if (BindingList.Count > 1)
{
BindingList.RemoveAt(0);
}
}
}
So the problem that if I just Run solution(ctrl + F5) all works fine, but in debug mod(F5) nothing happens when i press button. All answers I found say : "use lock" so i used lock and listbox still not react on adding elements to list. Please help me what i am doing wrong or where i missed something.
PS sorry for my English.
First, to be clear: you may or may not need to use lock here. That would depend on whether there are actually two or more threads accessing the BindingList<T> object concurrently, i.e. literally at the same time (e.g. two or more threads adding items to the list, or one thread adding items while another one is trying to read from the list). In your code example, this does not appear to be the case, and so it wouldn't be necessary. Regardless, the lock statement does something completely different than what is required to address the specific issue you're asking about, and in any case only works when threads use lock cooperatively on the same object (if only one thread calls lock, that doesn't help).
The basic issue is that the ListBox can't respond to events from BindingList when those events are raised on other than the UI thread. Typically, the solution to this would be to call Control.Invoke() or similar to execute the list-modifying operation in the UI thread. But in your case, the class that owns the BindingList isn't a UI object and so doesn't naturally have access to the Control.Invoke() method.
IMHO, the best solution preserves the UI thread knowledge in the UI object(s) involved. But doing it this way would require having the Class1 object hand over at least some of the control over the list to that UI object. One such approach would involve adding an event to the Class1 object:
public class AddItemEventArgs<T> : EventArgs
{
public T Item { get; private set; }
public AddItemEventArgs(T item)
{
Item = item;
}
}
public class Class1
{
public EventHandler<AddItemEventArgs<string>> AddItem;
public BindingList<string> BindingList { get; set; }
public Class1()
{
// Sorry, old-style because I'm not using C# 6 yet
BindingList = new BindingList<string>();
}
// For testing, I prefer unique list items
private int _index;
public void Add()
{
var th = new Thread(() =>
{
string item = (++_index).ToString();
OnAddItem(item);
}) { IsBackground = true };
th.Start();
}
public void Remove()
{
if (BindingList.Count > 1)
{
BindingList.RemoveAt(0);
}
}
private void OnAddItem(string item)
{
EventHandler<AddItemEventArgs<string>> handler = AddItem;
if (handler != null)
{
handler(this, new AddItemEventArgs<string>(item));
}
}
}
Then in your Form1:
public partial class Form1 : Form
{
private Class1 _class1;
public Form1()
{
InitializeComponent();
_class1 = new Class1(); // logic class instance
_class1.AddItem += (sender, e) =>
{
Invoke((MethodInvoker)(() => _class1.BindingList.Add(e.Item)));
};
listBox1.DataSource = _class1.BindingList;
}
private void button1_Click(object sender, EventArgs e)
{
_class1.Add();
}
private void button2_Click(object sender, EventArgs e)
{
_class1.Remove();
}
}
A variation on this theme would be to have two different "add" methods in Class1. The first would be the one you have now, which ultimately uses a thread. The second would be the one that would be required to be called from the UI thread, and which would actually add the item. In the AddItem event handler in the form, instead of adding the item directly to the list, the second "add" method would be called to do that for the form.
Which is best depends on how much abstraction you want in your Class1. If you're trying to hide the list and its operations from other classes, then the variation would be better. But if you don't mind updating the list from somewhere other than in the Class1 code, the code example above should be fine.
An alternative is to make your Class1 object thread-aware, similar to how e.g. BackgroundWorker works. You do this by capturing the current SynchronizationContext for the thread when the Class1 object is created (on the assumption that the Class1 object is created in the thread where you want to return to, to add an item). Then when adding an item, you use that context object for the add.
That looks like this:
public class Class1
{
public BindingList<string> BindingList { get; set; }
private readonly SynchronizationContext _context = SynchronizationContext.Current;
public Class1()
{
BindingList = new BindingList<string>();
}
private int _index;
public void Add()
{
var th = new Thread(() =>
{
string item = (++_index).ToString();
_context.Send(o => BindingList.Add(item), null);
}) { IsBackground = true };
th.Start();
}
public void Remove()
{
if (BindingList.Count > 1)
{
BindingList.RemoveAt(0);
}
}
}
In this version, no changes to Form1 are needed.
There are lots of variations on this basic scheme, including some which put the logic into a specialized BindingList<T> subclass instead. For example (to name a couple):
Cross-Thread Form Binding - Can it be done?
BindingList<> ListChanged event
Finally, if you want to really hack things together, you can just force the entire binding to reset any time the list has changed. In that case, you would not need to change Class1, but you would need to change Form1:
public partial class Form1 : Form
{
private Class1 _class1;
public Form1()
{
bool adding = false;
InitializeComponent();
_class1 = new Class1(); // logic class instance
_class1.BindingList.ListChanged += (sender, e) =>
{
Invoke((MethodInvoker)(() =>
{
if (e.ListChangedType == ListChangedType.ItemAdded && !adding)
{
// Remove and re-insert newly added item, but on the UI thread
string value = _class1.BindingList[e.NewIndex];
_class1.BindingList.RemoveAt(e.NewIndex);
adding = true;
_class1.BindingList.Insert(e.NewIndex, value);
adding = false;
}
}));
};
listBox1.DataSource = _class1.BindingList;
}
// ...
}
I don't really advise this approach. But if you have no way to change Class1, it's about the best you can do.

How to send object from one Frame to another

Using VS 2013, C#, Windows Store App
I need to send one object from main Frame to new one and then work with it.
So i have main Frame, second Frame (for work with sended object) and DataModel.
Idea - is to display all data that i have at main frame, than choose one object, press on it, after pressing new Frame will appear and you can work with selected items in new frame.
Problem - how to send object from one Frame to another.
Currently i made next: create additional static class that with static property:
public static class GetCurrentEvent
{
public static Event CurrentEvent { get; set; }
}
So, at first i call to property of this class at main Frame, and save required object using it:
private void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
var clickedItems = (Event)e.ClickedItem;
GetCurrentEvent.CurrentEvent = new Event(
clickedItems.UniqueId,
clickedItems.Name,
clickedItems.Place,
clickedItems.Description,
clickedItems.Start,
clickedItems.End,
clickedItems.ImagePath
);
if (this.Frame != null)
{
this.Frame.Navigate(typeof(ChangeEvent));
}
}
After that i use this property in new Frame:
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
this.DataContext = GetCurrentEvent.CurrentEvent;
...
}
All works, but i think that it's not the perfect method.
So,the quesion how i can change code abowe for improving methods, or how i can send object from one class to another?
EDIT
Choosed varinat to send object from Frame to Frame - use Parameter:
var clickedItems = (Event)e.ClickedItem;
this.Frame.Navigate(typeof(ChangeEvent), clickedItems);
and then convert to required type in new Frame:
this.DataContext = (Event)e.NavigationParameter;
There are many many ways that this accomplished and this tends be an opinionated debate.
I typically opt for a simple solution, such as saving a state/session variable in a global accessible singleton. I call the singleton Global and keep it in the root of the namespace.
Example:
public sealed class Global
{
#region Singlton Contructor
Global() { }
static readonly Global instance = new Global();
public static Global Default
{
get { return instance; }
}
#endregion
#region Global Settings
public Settings Settings {get;set;}
private AuthenticatedUser _authenticatedUser;
public AuthenticatedUser AuthenticatedUser
{
get
{
return _authenticatedUser;
}
set { _authenticatedUser = value; }
}
private UserSession _currentSession;
public UserSession CurrentSession
{
get
{
if (_currentSession == null) _currentSession = UserSessionService.UserSessionFactoy();
return _currentSession;
}
private set { _currentSession = value; }
}
#endregion
}
CurrentSession in this case keeps track of the objects I want to pass frame to frame. And its easily accessed by using
Global.CurrentSession.SomePropertyOrObject

Updating another ViewModel on PropertyChanged of another ViewModel

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.

C# Multiple class events

Im making a program what connects to multiple 3th party systems. The connect with different formats so i created multiple classes to deal with them. I have now three 4 classes.
The MainForm is the first class. This is the basic windows form class with the user interface.
SDKCommunication is the second class.
VMS (this class handles the events given of by the 2th party system and activates methods on SDK COmmunication)
Events
Events Class
public class Events
{
public event EventHandler LoginStateChanged;
private bool loginstate;
public bool LogInState
{
get { return this.loginstate; }
set
{
this.loginstate = value;
if (this.LoginStateChanged != null)
this.LoginStateChanged(this, new EventArgs());
}
}
}
part of SDKCommunicatie class
Events events = new Events();
public void onLogon(string username, string directory, string system)
{
events.LogInState = false;
}
MainForm Class
SDKCommunicatie sdkcommunicatie = new SDKCommunicatie();
Events events = new Events();
public MainForm()
{
InitializeComponent();
events.LoginStateChanged += new EventHandler(events_LoginStateChanged);
}
void events_LoginStateChanged(object sender, EventArgs e)
{
log.Info("EventFired loginstateChanged");
}
When the LogInState Changes in the SDKCommunicatie class. There needs to be an event fired in the MainForm class. But sadly that doesn't work.
But when I change the loginstate in the mainform(with a buttonclick)(see code below) the event is fired. But that is not the intention i would like to have.
private void button1_Click(object sender, EventArgs e)
{
events.LogInState = true;
}
If my question isn't clear enough, please let me know.
VMS class Added as reply to #Astef
class VMS {
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(MainForm));
GxUIProxyVB m_UIProxy = new GxUIProxyVB();
public string username2;
public string directory2;
public string Status;
public void initOmni()
{
m_UIProxy.CreateInstance();
m_UIProxy.OnLogon += new _IGxUIProxyVBEvents_OnLogonEventHandler(m_UIProxy_OnLogon);
m_UIProxy.OnLogoff += new _IGxUIProxyVBEvents_OnLogoffEventHandler(m_UIProxy_OnLogoff);
m_UIProxy.OnError += new _IGxUIProxyVBEvents_OnErrorEventHandler(m_UIProxy_OnError);
m_UIProxy.OnAlarmStatusEx2 += new _IGxUIProxyVBEvents_OnAlarmStatusEx2EventHandler(m_UIProxy_OnAlarmStatusEx2);
}
public void login(string username, string password, string directory)
{
username2 = username;
directory2 = directory;
initOmni();
m_UIProxy.LogOn(directory, username, password,false);
}
public void logOff()
{
m_UIProxy.LogOff();
}
void m_UIProxy_OnLogon()
{
SDKCommunicatie sdkcommunicatie = new SDKCommunicatie();
sdkcommunicatie.onLogon(username2, directory2, "Genetec Omnicast");
}
I have fixed this with deleting the following:
SDKCommunicatie sdkcommunicatie = new SDKCommunicatie();
And adding the following in the base of VMS:
SDKCommunicatie sdkcommunicatie;
But now i got a new error in the mainform when i tried to call a class in SDKCommunicatie
connectedStatus = sdkcommunicatie.connectedStatus();
I got the following error:
NullReferenceException was unhandled
You are not using the same instance of the Events class, and that's why on button click you catch LoginStateChanged. You should inject the same instance of Events class to SDKCommunicatie class, then you'll be able to listen to event changes.
Edit:
Jeremy Todd and I were both writing at the same time.
Events in your SDKCommunicatie are not fired because you've created an individual instance of class Events for it. That is not the instance you have placed on the MainForm.
Inject the right instance (pass a reference) to SDKCommunicatie from MainForm through constructor, property or somehow else. For example:
MainForm:
SDKCommunicatie sdkcommunicatie;
Events events = new Events();
public MainForm()
{
InitializeComponent();
events.LoginStateChanged += new EventHandler(events_LoginStateChanged);
sdkcommunicatie = new SDKCommunicatie(events);
}
void events_LoginStateChanged(object sender, EventArgs e)
{
log.Info("EventFired loginstateChanged");
}
SDKCommunicatie:
Events events;
public SDKCommunicatie(Envents eventsInstance)
{
events = eventsInstance;
}
public void onLogon(string username, string directory, string system)
{
events.LogInState = false;
}
Your SDKCommunication class and your MainForm class each have their own separate instance of Events, so any events you trigger from one won't be visible from the other -- they're being raised on an entirely different object.
What you need is a single instance of the Events class that both SDKCommunication and MainForm can share -- that way they'll both be seeing the same thing. There are several different approaches you could take for this. Depending on what it needs to do, one very simple possibility might be to make Events a static class, and then the events would be visible everywhere without needing to create any instances.
I have solved the riddle.
When i need a method is a class i can call the method directly like this:
public class MainForm : Form
{
SDKCommunication sdkcommunication = new SDKCommunication();
public MainForm()
{
}
private void Button1_Click(oject sender, EventArgs e)
{
sdkcommunication.method("Test")
}
}
This is pretty straightforward. Look here the receiverclass:
public class SDKCommunication
{
method(string word)
{
//do something with word
}
}
The biggest problem is calling the class with the form(the original class). I have solved this with a eventhandler.
class CustomEventHandler1 : EventArgs
{
public CustomEventHandler1(string u, string d)
{
msgu = u;
msgd = d;
}
private string msgu;
private string msgd;
public string Username
{
get { return msgu; }
}
public string Directory
{
get { return msgd; }
}
}
Then the SDKCOmmunication class should look like this:
class SDKCommunication
{
public event EventHandler<CustomEventHandler1> RaiseCustomEventHandler1;
protected virtual void OnRaiseCustomEventHandler1(CustomEventHandler1 e)
{
EventHandler<CustomEventHandler1> handler = RaiseCustomEventHandler1;
if (handler != null)
{
handler(this,e);
}
}
//Custom Method that is called somewhere
internal void custommethod()
{
OnRaiseCustomEventHandler1(new CustomEventHandler1("johnsmith", "localhost");
}
}
Then in the mainform class:
public class MainForm : Form
{
public MainForm()
{
sdkcommunication.RaiseCustomEventHandler1 += new EventHandler<CustomEventHandler1>(sdkcommunication_RaiseCustomEventHandler1);
}
void sdkcommunication_RaiseCustomEventHandler1(object sender, CustomEventHandler1 e)
{
//Do something.
}
}
The information sended with the event you can get with e.Username and e.Directory. In this example they are strings where e.Username = johnsmith and e.Directory = localhost.
I hope somebody can use this information for their own code.

Categories

Resources