As you can see, I have two classes. RfidReaderHardware generates event in thread "th", but Form running at another thread. As you can see, in form if use Invoke method of ListViewControl. So, question is how to change RfidReaderHardware to resolve encapsulation problem.
public class RfidReaderHardware : IDisposable
{
public event EventHandler<RfidReaderEventArgs> OnNewPackage;
Thread th;
//This method will be called from thread "th"
private void FireNewPackageEvent(UHFPackage package)
{
... code ...
}
... some code ...
}
and we have example code, where this event is using
public partial class PassageForm : Form
{
RfidReaderHardware RfidReader = new RfidReaderHardware(...);
private void Form1_Load(object sender, EventArgs e)
{
RfidReader.OnNewPackage += NewRfidPackage;
}
//not sure, but i think it's running in thread "th"
private void NewRfidPackage(Object o, RfidReaderEventArgs e)
{
ListViewItem item = new ListViewItem();
//from point of encapsulation view it's wrong as you know
CPackageList.Invoke(new Action(() => {CPackageList.Items.Add(item); }));
}
}
question is how to change RfidReaderHardware to resolve encapsulation problem
In fact there is no encapsulation problem. By definition, the relation between event source and subscriber is one to many, hence the source cannot "encapsulate" a logic for a specific subscriber. It's the subscriber choice how to handle the notification. One can ignore it, or handle it immediately, or like in your case handle it on the UI thread either synchronously (using Control.Invoke) or asynchronously (using Control.BeginInvoke).
Not so sure there's any real need to fix this, having the UI object itself deal with the fact that event is fired on the "wrong" thread is not a flaw. As long as you know it is in fact fired on the wrong thread, a documentation requirement.
.NET however has a general mechanism to solve this, it is used in several places inside the .NET Framework code. Your RfidReaderHardware class constructor can copy the value of SynchronizationContext.Current and store it in a field. With the implicit assumption that the object is created by code that runs on the UI thread. When you are ready to fire the event, and the copied object isn't null, you can then use its Post() or Send() method. Which automagically makes the code resume on the UI thread. Regardless of the specific UI class library that was used, works just as well in a WPF or Universal app for example.
Some sample code, it doesn't take much:
public class RfidReaderHardware {
public event EventHandler Received;
public RfidReaderHardware() {
syncContext = System.Threading.SynchronizationContext.Current;
}
protected void OnReceived(EventArgs e) {
if (syncContext == null) FireReceived(e);
else syncContext.Send((_) => FireReceived(e), null);
}
protected void FireReceived(EventArgs e) {
var handler = Received;
if (handler != null) Received(this, e);
}
private System.Threading.SynchronizationContext syncContext;
}
Related
Relatively new to C# and coding in general (first post here). I have a WinForms local application where some information is displayed to the user in a ReadOnly(true) RichTextBox. Almost all my classes need to send information to that RichTextBox. To simplify this process, I created a method inside a static class that uses a locked delegate to send the information to that RichTextBox. Here is a sample:
static class MyClass
{
public delegate void MessageReceivedEventHandler(string message);
public static event MessageReceivedEventHandler messageReceivedEventHandler;
public static void MessageBox(string message)
{
lock (messageReceivedEventHandler)
{
//Thread.Sleep(20);
messageReceivedEventHandler?.Invoke(message);
}
}
}
partial class MyForm : Form
{
public MyForm()
{
MyClass.messageReceivedEventHandler += OnMessageReceived;
}
private void OnMessageReceived(string message)
{
richTextBox1.Text = richTextBox1.Text.Insert(0, $" {message}\n");
}
private void Button1_click()
{
MyClass.MessageBox("This should be working!");
//Add more work here...
}
}
The code above would simply print "This should be working!" inside the RichtTextbox.
The problem is the text from richTextBox1 sometimes becoming empty. This issue seems to appear when the MessageBox method is being called in rapid succession. My assumption was that since I have diffent Tasks running at the same time (in other parts of my code), it probably is two Tasks attempting to use the same static ressource, hence the use of Lock. But I still have the issue.
Adding the Thread.Sleep(20) seems to fix the problem, but that is far from elegant/robust. It starts breaking up again when the time inside Sleep is <10ms.
Edit 1:
To clarify what I mean by "string becoming empty", it means the text from richTextBox1 is == "" at some points, which should not happen since the code is always inserting the text, not replacing it. The OnMessageReceived method is the only place where action is taken on the RichTextBox text.
Edit 2:
I saw many questions related to the other tasks running. First, yes it is a multi-threaded application. The only relation between those tasks and my main form is the "print" function I wrote above. To give more context, this application is used to control the position of stepper motors relative to an electrical signal. When doing so, I need to print important information in my main form. This is why losing the information in my RichTextBox (where I print the information) is an issue. The possible reason of why I am losing the text inside that RichTextBox should be the focus of this thread.
Keep in mind that this is a personnal side project, and not a large scale application.
Thanks,
Laurent
There are multiple problems in your code.
First, you should not lock on a public object, since that allows other threads to lock on the same object, risking interlocking your threads. Second, your symptoms suggest multiple threads are trying to access the ressources. Rather than depending on complex thread locking code, you'd rather schedule UI operations on the UI context, which will allow calling adding message from background tasks.
The best way to do that is to that is by using Control.BeginInvoke()
You can't copy your form instance everywhere, so we'll expose a static method. You could make the class a singleton, but if you need multiple instances that won't work. I'll give a more versatile example. When the static method is called, you don't have access to the form instance anymore, so we'll use IOC pattern with an event and delegate.
Let's make a private static event that all instances will register a callback to in the constructor. When the static method raises the static event, all instances callback will be called. The callback will schedule a modification of its text box.
partial class MyForm : Form
{
private class MessageWriteRequestedEventArgs : EventArgs
{
public string Message { get; }
public MessageWriteRequestedEventArgs(string message)
{
Message = message;
}
}
private static event EventHandler<MessageWriteRequestedEventArgs> MessageWriteRequested;
public MyForm()
{
MessageWriteRequested += OnMessageWriteRequested;
}
public static void WriteMessage(string message)
{
MessageWriteRequested?.Invoke(this, new MessageWriteRequestedEventArgs(message));
}
private void OnMessageWriteRequested(object sender, MessageWriteRequestedEventArgs e)
{
richTextBox1.BeginInvoke(() => WriteMessageSafe(e.message));
}
private void WriteMessageSafe(string message)
{
richTextBox1.Text = richTextBox1.Text.Insert(0, $" {message}\n");
}
private void Button1_click()
{
// you're on ui context, you're safe to access local ui resources
WriteMessageSafe("This should be working!");
// if you have multiple MyForm instances, you need to use the event
WriteMessage("Broadcasting my tralala");
}
}
If you need to write to the textbox from anywhere else :
// do stuff
MyForm.WriteMessage("Ho Ho Ho !");
.NET already includes a class for reporting progress (or any other information) from an asynchronous operation in a thread-safe manner, Progress< T>. It doesn't need locking and even better, it decouples the sender and receiver. Many long-running BCL operations accept an IProgress<T> parameter to report progress.
You haven't explained what's going on in the form, or what task is reporting the data. Assuming the producer is another method in the same form, you could create a Progress<T> instance in the same method that starts the async operation, eg :
async void Button1_Click()
{
var progress=new Progress<string>(ReportMessage);
ReportMessage("Starting");
await Task.Run(()=>SomeLongOp(progress));
ReportMessage("Finished");
}
void SomeLongOp(IProgress<string> progress)
{
for(int i=0;i<1000000;i++)
{
...
progress.Report($"Message {i}");
...
}
}
void ReportMessage(string message)
{
richTextBox1.Text = richTextBox1.Text.Insert(0, $" {message}\n");
}
By using IProgress< T>, the SomeLongOp method isn't tied to a specific form or global instance. It could easily be a method on another class
Publishing lots of messages
Let's say you have a lot of workers, doing a lot of things, eg monitoring a lot of devices, and want all of them to publish messages to the same Log textbox or RTF box. Progress< T> "simply" executes the reporting delegate or event handler on its original sync context. It doesn't have an asynchronous Report method, nor can it queue messages. In a really high-traffic environment, the synchronization switch can delay all workers.
The built-in answer to this is to use one of the pub/sub classes like ActionBlock< T> or a Channel.
An ActionBlock< T> processes the messages in its input queue in order, using a worker task that runs on the ThreadPool by default. This can be changed by specifying a different TaskScheduler in its execution options. By default, its input queue is unbounded.
One could use an ActionBlock to receive messages from multiple workers and display them on a textbox. The block can be created in the constructor, and passed to all workers as an ITargetBlock<T> interface :
ActionBlock<string> _logBlock;
public MyForm()
{
var options=new ExecutionDataFlowBlockOptions {
TaskScheduler=TaskScheduler.FromCurrentSynchronizationContext();
};
_block=new ActionBlock<string>(ReportMessage,options);
}
Now the fun begins. If the workers are created by the form itself, the workers can publish to the block directly :
public async void Start100Workers_Click(...)
{
var workers=Enumerable.Range(0,100)
.Select(id=>DoWork(id,_block));
await Task.WhenAll(workers);
}
async Task DoWork(int id,ITargetBlock<string> logBlock)
{
.....
await logBlock.SendAsync(message);
...
}
Or the block could be exposed through a public property, so other classes/forms in the application can post to it.
public ITargetBlock<string> LogBlock=>_block;
I'm going to show a simple way to do what I think you're after.
I started with a .NET Core 3.1 Win forms application. I added a rich text control to the form. I added a button to the form.
I added a TaskCompletionSource as a instance property - this will be used to control the tasks acting as workers which you described.
CancellationTokenSource sharedCancel = new CancellationTokenSource();
I created an interface to represent something that accepts messages as you described:
public interface IMyMessageSink
{
Task ReceiveMessage(string message);
}
I made my form support this interface.
public partial class Form1 : Form, IMyMessageSink
The ReceiveMessage method looks like this:
public Task ReceiveMessage(string message)
{
if(this.sharedCancel == null || this.sharedCancel.IsCancellationRequested)
return Task.FromResult(0);
this.Invoke(new Action<Form1>((s) => this.richTextBox1.Text = this.richTextBox1.Text.Insert(0, $"{message}\n")), this);
return Task.FromResult(0);
}
You'll see the Invoke handles the synchronization back to the UI thread.
This should probably use BeginInvoke and then convert the APM to async tasks which you can read about here. But for an SO answer the above simple code will suffice.
Also note there's no error handling. You'll want to add that to your generator and to the button handler.
Next I created a class to represent something that creates messages. This class takes the interface created and the cancellation token. It looks like this:
public class MyMessageGenerator
{
CancellationToken cancel;
IMyMessageSink sink;
public MyMessageGenerator(CancellationToken cancel, IMyMessageSink sink)
{
this.cancel = cancel;
this.sink = sink;
}
public async Task GenerateUntilCanceled()
{
try
{
while (!this.cancel.IsCancellationRequested)
{
await sink.ReceiveMessage(this.GetHashCode().ToString());
await Task.Delay(5000, this.cancel);
}
}
catch (OperationCanceledException)
{ }
}
}
In the button handler we create the message generators.
async void button1_Click(object sender, EventArgs e)
{
if (null == this.sharedCancel)
return;
await Task.Run(() => new MyMessageGenerator(this.sharedCancel.Token, this).GenerateUntilCanceled());
}
Finally I added an override for the form closing event:
protected override void OnClosing(CancelEventArgs e)
{
if (null != this.sharedCancel)
{
this.sharedCancel.Cancel();
this.sharedCancel.Dispose();
this.sharedCancel = null;
}
base.OnClosing(e);
}
If the application becomes larger and more complex you would likely benefit by adding services exposed using a DI container. You can read about adding DI to a winforms app here.
Should the event subscriber always register the event handler, or is it ok for another class to do it?
Example:
class EventPublisher {
public event EventHandler Event;
}
class EventSubscriber {
public void Handler(object sender, EventArgs e) { }
}
class Glue {
private EventPublisher _publisher = new EventPublisher();
private EventSubscriber _subscriber = new EventSubscriber();
public Glue() {
_publisher.Event += _subscriber.Handler;
}
}
Should the event subscriber always register the event handler, or is
it ok for another class to do it?
There is nothing inherently wrong with this design per se, though it really depends on your architecture and how scaleable and decoupled you want it.
Just a note, there is no context with the generic hypothetical you have put forward with Glue so its hard to tell your exact requirements.
...
These days, i rarely write traditional events and tend to use a more modern approach of Pub/Sub Producer/Consumer Decoupled Messages or an Event Aggregator (depending on the frameworks you are using). Consumers can subscriber at will, and producers can publish at will without any prior knowledge of each other. Martin Follower goes into this pattern in more detail on his site.
More-so, the advantages with Decoupled Messages (and kin), is consumers can subscribe to a conversation without having any previous knowledge of who the producers are, which gives you better decoupling which in turn creates a more maintainable and scalable system. If the system becomes large enough, pushing classes to Microservices can be a lot less painful.
On saying that, if this is only a very simple implementation (and tightly coupled implementation) there is nothing wrong with standard vanilla ice-cream flavored .NET events and having your subscriber register the event handler, though once again you have to break this down into the most logical concern for your design.
I.e should a SoundManager inherently know about a dog and its bark. Or should your dog class register to the sound manager. Your design intuition should lead the way
Anyway, good luck.
Programmatic, it is not wrong and will work smooth for you.
In fact you need to do this in the case where in EventSubscriber of which method
(Handler)should be called on Event , doesn't have object of EventPublisher class.
How to subscribe events is subjective matter, it depends on your over all flow and comfort.
But According to me, subscribing event in side of subscriber class make code more readable and easy to understand
you see to subscribe an event of any class, subscriber class must have an object of that class, but here in your example it doesn't have.
Generally people design architecture in such method when subscriber class itself hold the object of publisher class. This design has its own benefits in some flow, see below example,
public class EventPublisher
{
public event EventHandler HeavyLogicDone;
public void ExposedMethod(string subScriberSpecificData)
{
Thread logicCaller = new Thread(() => HeavyLogic(subScriberSpecificData));
logicCaller.Start();
}
private void HeavyLogic(string subScriberSpecificData)
{
//logic which may take time
if (HeavyLogicDone != null)
HeavyLogicDone(this, new EventArgsClass(subScriberSpecificData));
}
}
here EventPublisher class has a publicly exposed functionality, which should be called by EventSubscriber , but as this method may take time, it is being written in thread.
Now problem, as it is in thread, call of this method will return soon after starting the thread, subscriber can not start its functionality which is dependent on this method's result, it must wait. So to notify subscriber that task has been done, there is an event.
public class EventSubscriber
{
string currentData = "";
public EventSubscriber(EventPublisher eventPublisher, string data)
{
currentData = data;
eventPublisher.HeavyLogicDone += eventPublisher_HeavyLogicDone;
eventPublisher.ExposedMethod(currentData);
//Contineous without waiting for heavy logic to compelete
}
void eventPublisher_HeavyLogicDone(object sender, EventArgs e)
{
if(((EventArgsData)e).subScriberSpecificData == currentData)
{
//Do further task which is dependant to result of logic
//if now subscriber doesn't need to listen this event anymore
((EventPublisher)sender).HeavyLogicDone -= eventPublisher_HeavyLogicDone;
}
}
}
As you can seen when to subscribe an event and when to unsubscribe it, subscriber has all control now.
But, if you would be doing like this.
static void Main(string[] args)
{
EventSubscriber subscriber1 = new EventSubscriber("sub1");
EventSubscriber subscriber2 = new EventSubscriber("sub2");
EventPublisher pub = new EventPublisher();
pub.HeavyLogicDone += subscriber1.eventPublisher_HeavyLogicDone;
pub.HeavyLogicDone += subscriber2.eventPublisher_HeavyLogicDone;
pub.ExposedMethod("sub1");
pub.ExposedMethod("sub2");
}
First problem : As you can see, every time you are creating an instance of Subscriber, you have to explicitly write it's subscription and calling of publisher's method for current subscriber. that is coupled code, and you need to keep doing this every time.
and subscriber class
public class EventSubscriber
{
string currentData = "";
public EventSubscriber(string data)
{
currentData = data;
}
public void eventPublisher_HeavyLogicDone(object sender, EventArgs e)
{
if(((EventArgsData)e).subScriberSpecificData == currentData)
{
//Do further task which is dependant to result of logic
//if now subscriber doesn't need to listen this event anymore
((EventPublisher)sender).HeavyLogicDone -= eventPublisher_HeavyLogicDone;
}
}
}
Second problem : as subscription is not in control of subscriber class, and unsubscribing can be done only withing subscriber class. Code will be little mess to understand.
I have Windows Forms application with one main form (derived from base Form). Other modal forms that could be opened there are derived from my class ManagedForm, which is also derived from Form.
Also I have a static notifier service which fires some events like this:
public static class NotifierService
{
public delegate void NotifierServiceEventHandler(object sender, NotifierServiceEventArgs e);
private static readonly object Locker = new object();
private static NotifierServiceEventHandler _notifierServiceEventHandler;
#region Events
public static event NotifierServiceEventHandler OnOk
{
add
{
lock (Locker)
{
_notifierServiceEventHandler += value;
if (
_notifierServiceEventHandler.GetInvocationList()
.Count(
_ =>
_.Method.DeclaringType != null &&
value.Method.DeclaringType != null &&
_.Method.DeclaringType == value.Method.DeclaringType) <= 1)
return;
_notifierServiceEventHandler -= value;
}
}
remove
{
lock (Locker)
{
_notifierServiceEventHandler -= value;
}
}
}
// and many more events similar to previous...
#endregion
#region Event firing methods
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
NotifierServiceEventHandler handler;
lock (Locker)
{
handler = _notifierServiceEventHandler;
}
if (handler == null) return;
handler(typeof (NotifierService),
new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
#endregion
}
So in some places of code these events could be fired like:
NotifierService.NotifyExclamation("Fail!");
In the main form there is StatusStrip control used for notification purposes, and due to main form has subscribtion to these events -- their messages will be shown in the status strip.
BUT!, as I've said earlier, user may open other forms, and these forms could produce others and so on... (they are derived from one class ManagedForm which will be subscribed to NotifierService as soon as it has been created).
In these forms there is another logic how to notify user -- they need to show MessageBoxes with messages. As you can see, I've added some magic in event accessors to allow only one subscriber of any type, because w/o this all opened forms will generate their own MessageBoxes. But when one child ManagedForm has produced another and the second has been closed -- no MessageBoxes will be shown.
What magic should I implement to allow subscription from only first ManagedForm? Many thanks for any ideas.
EDIT: Suggested ideas doesn't solve this issue. I've tried to change event to this:
private static readonly object Locker = new object();
private static EventHandler<NotifierServiceEventArgs> _myEvent;
public static event EventHandler<NotifierServiceEventArgs> OnOk
{
add
{
if (_myEvent == null || _myEvent.GetInvocationList().All(_ => _.Method.DeclaringType != value.Method.DeclaringType))
{
_myEvent += value;
}
}
remove
{
_myEvent -= value;
}
}
Then I've open one modal child form and create a situation in which event has been fired by NotifierService. One MessageBox has been generated and shown (that's OK). Afterwards I've opened another modal form from first and create another situation in which another event has been fired. One MessageBox has been generated and shown (that's also OK). Now I'm closing second form and making a situation needed to fire event. No MessageBoxes has been shown (but in the status strip of the main form message of event has been shown correctly, so nothing has been changed from my first implementation).
Should I change something in remove clause? I do not need that only one subscriber should be, I need that each of the subscribers should be of distinct types. Sorry If bad English.
The way you are trying to solve the problem is fundamentally wrong by design. Your service class defines an event that will be fired under some circumstances. Some clients subscribe to that event, this way requesting to be notified when it happened. This is simply the .NET way of implementing the Observer pattern, so your service (being the subject or observable), should not apply any logic neither at subscribe nor the notify part, thus defeating the whole purpose of the pattern. Hans Passant already pointed to some flaws in your design, but even his solution is not perfect because looking at the event signature, it's totally unclear that only form instance methods are supposed to be registered - one can try using static method, anonymous lambda/method, some class method etc.
So, IMO the following are some of the viable choices you have.
(A) Keep your NotificationService events, but remove any "magic" from both subscribe and notify parts (shortly, use the regular way of defining and firing an event) and put the logic needed in your subscribers:
public static class NotifierService
{
public delegate void NotifierServiceEventHandler(object sender, NotifierServiceEventArgs e);
public static event NotifierServiceEventHandler OnOk;
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
var handler = OnOk;
if (handler != null)
handler(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
}
Assuming that only the active form is supposed to handle the notifications, the existing handlers in both your MainForm and ManagedForm would use something like this inside their method body
if (this != ActiveForm) return;
// do the processing
You can even create a base form like this
class NotifiedForm : Form
{
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
NotifierService.OnOk += OnNotifyOK;
// similar for other events
}
protected override void OnDeactivate(EventArgs e)
{
base.OnDeactivate(e);
NotifierService.OnOk -= OnNotifyOK;
// similar for other events
}
protected virtual void OnNotifyOK(object sender, NotifierServiceEventArgs e) { }
// similar for other events
}
and let your MainForm, ManagedForm (and any other is needed) inherit from that and just override the OnNotifyXXX methods and apply their logic.
To conclude, this approach would keep your service abstract and will leave the decisions to the clients of the service.
(B) If the sole purpose of your service is to act like a notification coordinator specifically for your forms, then you can remove events along with subscribe/unsubscribe parts (since Application.OpenForms and Form.ActiveForm already provide enough information needed) and handle the logic in your service. In order to do that, you'll need some sort of a base interface(s) or forms, and the easiest would be to use a similar approach to what was optional in the option (A) by creating a base form class like this
class NotifiedForm : Form
{
public virtual void OnNotifyOK(object sender, NotifierServiceEventArgs e) { }
// similar for other notifications
}
and let your MainForm, ManagedForm and other needed inherit from it. Note that there is no logic here (checking ActiveForm etc.) because now that's the responsibility of the caller. Then the service could be something like this:
public static class NotifierService
{
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
var target = Form.ActiveForm as NotifiedForm;
if (target != null)
target.OnNotifyOK(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
// similar for other notifications
}
if the logic is to notify only the active form.
Or
public static class NotifierService
{
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
// Could also be a forward for, forach etc.
for (int i = Application.OpenForms.Count - 1; i >= 0; i--)
{
var target = Application.OpenForms[i] as NotifiedForm;
if (target != null /* && someOtherCritaria(target) */)
{
target.OnNotifyOK(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
// Could also continue
break;
}
}
}
// similar for other notifications
}
if some other logic is needed (which I doubt).
Hope that helps. In any case, option (A) is more flexible and allows much more usage scenarios, but if the usage scenarios are fixed by design, then the option (B) is better because it requires less from the clients (thus being less error prone) and provides a centralized application logic in one place.
I would like you proceed as follows:
Remove the magic from event accessor method and let all the subscribers subscribe to the event. So now you will have your main form and all other forms subscribed to the event.
Now place the magic in your event invocation method. For example in your NotifyOK method, first get the invocation list of deligate, now invoke each deligate one by one using DynamicInvoke or Invoke method of each deligate in the invocation list only if you have not already invoked for the particular DeclaringType. See the algo below:
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
NotifierServiceEventHandler handler;
lock (Locker)
{
handler = _notifierServiceEventHandler;
}
if (handler == null) return;
// Get invocation list of handler as you have done in event accessor
//initialise a new List<T> to hold the declaring types
// loop through each member (delegate) of invocation list
// if the current member declaration type is not in List<t>
// Invoke or DynamicInvoke current delegate
// add the declaration type of current delegate to List<t>
}
Try this:?)
private bool _eventHasSubscribers = false;
private EventHandler<MyDelegateType> _myEvent;
public event EventHandler<MyDelegateType> MyEvent
{
add
{
if (_myEvent == null)
{
_myEvent += value;
}
}
remove
{
_myEvent -= value;
}
}
i have reduced NotifierService to this:
public static class NotifierService
{
public static event EventHandler<NotifierServiceEventArgs> OnOk = delegate { };
public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
{
OnOk(typeof(NotifierService),
new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
}
}
and then in ManagedForm used this handler
NotifierService.OnOk += Notify;
private void Notify(object sender, NotifierServiceEventArgs e)
{
// handle event in first open ManagedForm
if (Application.OpenForms.OfType<ManagedForm>().FirstOrDefault() == this)
{
// notification logic
}
}
if forms are opened as Modal (using ShowDialog()), it is possible to use another variant (according to this question):
private void Notify(object sender, NotifierServiceEventArgs e)
{
// handle event in active (last shown) ManagedForm
if (this.CanFocus)
{
// notification logic
}
}
so the idea is that all ManagedForms receive event data and then decide should they do something or not
P.S.: unsubscribe handlers on Dispose
protected override void Dispose(bool disposing)
{
if (disposing)
{
NotifierService.OnOk -= Notify;
}
// default
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
I have made a setup similar to yours & I see the problem.
I'll give 2 working suggestion to fix the issue (you may choose as per the changes required) -
Quickest fix with minimal changes to your original code -
So this is what I understand from the problem situation - You hooked event NotifierService.OnOk to an event handler in class ManagedForm & also wrote code to unhook the event handler from event NotifierService.OnOk when the form closes.
I'm assuming that you wrote the code to unhook the event handler from event NotifierService.OnOk when the form closes
But what I'm not sure is that when do you hook event NotifierService.OnOk to its event handler in managed form. Thats critical & I guess thats the only problem in your setup.
I assume you have set it up at a place which happens only once in the lifetime of form - like constructor or Load Event handler. And thats how I could reproduce the problem.
As fix, Just move hooking the event NotifierService.OnOk to its event handler at a place which which is called everytime the form becomes active
like
something like this -
public partial class ManagedFrom : Form
{
// this is the fix. Everytime the form comes up. It tries to register itself.
//The existing magic will consider its request to register only when the other form is closed or if its the 1st of its type.
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
NotifierService.OnOk += NotifierService_OnOk;
}
No more change needed, your existing logic in the event will take care of rest.
I have written the reason as comment in code above.
A little Better way but needs more changes
I would like to relieve the event OnOk form all the additional (& magical) responsibilities, I change the event
public static event NotifierServiceEventHandler OnOk
{
add
{
lock (Locker) // I'm not removing the locks. May be the publisher works in a multithreaded business layer.
{
_notifierServiceEventHandler += value;
}
}
remove
{
lock (Locker)
{
_notifierServiceEventHandler -= value;
}
}
}
Instead the subscriber should know when to Start and when to stop the subscription.
Therefore I change ManagedFrom
public partial class ManagedFrom : Form
{
//start the subscription
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
NotifierService.OnOk += NotifierService_OnOk;
}
//stop the subscription
protected override void OnDeactivate(EventArgs e)
{
base.OnDeactivate(e);
NotifierService.OnOk -= NotifierService_OnOk;
}
In both the suggestions, my intend is to just fix the issue without introducing any new pattern. But do let me know if thats needed.
Also do let me know if it was helpful or if you think I took any wrong assumption .
To sum up:
there are multiple sources of events;
there are multiple targets;
there are different types of events which have to be processed differently.
Idea to use static manager is ok (unless you have performance issues, then splitting into multiple different message queues is the option), but cheating with subscribing/unsubscribing feels so wrong.
Make a simple event
public enum MessageType { StatusText, MessageBox }
public NotifyEventArgs: EventArgs
{
public MessageType Type { get; }
public string Message { get; }
public NotifyEventArgs(MessageType type, string message)
{
Type = type;
Message = message;
}
}
public static NotifyManager
{
public event EventHandler<NotifyMessageArgs> Notify;
public static OnEventHandler(MessageType type, string message) =>
Notify?.Invoke(null, new NotifyEventArgs(type, message));
}
Each form has to subscribe to this event when shown and unsubscribe when hidden. Not sure which events are the best here (got used to much to WPF Loaded, Unloaded, but there is no such in winforms, try to use Shown or VisibilityChanged perhaps).
Each form will receive event, but only one has to process MessageBox type (it is safe for all of them to display StatusMessage). For this you need some mechanizm to decide whenever form is the one (used to display message boxes). E.g. it can be active form:
void NotifyManager_Event(object sender, NotifyEventArgs e)
{
if(e.Type == MessageType.MessageBox && this == Form.ActiveForm)
MessageBox.Show(this, e.Message);
else
statusBar.Text = e.Message;
}
Are you sure that it is the task of the NotifierService to make sure that only one Form will show the notification?
If you would describe the tasks of a NotifierService, you would describe what it does and "whenever the NotifierService has something to notify, it will notify everyone who said that it wanted to be notified about the notifications"
This would make your notifierservice less dependant of the current application where it is used. If you want a completely different application with for instance only two Forms, where you want both Forms to react on the notifications you could not use this notifierservice.
But in my Forms application only one form may react on the notifications
That is right: it is your Forms application that has this constraint, not the notifierservice. You make a Forms aplication that may use any kind of notifierservice, but whatever notifierservice is used, only one of the Forms in my application may show the notification.
This means that you should have some rule to know whether a form should show the notifications or not
For instance:
Only the current form may show the notifications
Only the top left form may show the notifications
Only the main form may show the notifications, except when the settings form is visible
So let's assume you have something to determine which Form or Forms may react on notifications. This changes upon something happening: a form becomes active, or a form closes, a form becomes invisible, whatever.
Make a Boolean property for a ManagedForm that holds whether it should show notifications:
class ManagedForm
{
public bool ShowNotifications {get; set;}
public void OnEventNotification(object sender, ...)
{
if (this.ShowNotifications)
{
// show the notification
}
}
Now someone has to know which form should show the notification. This someone should set property ShowNotification.
For instance if only the active ManagedForm should show the notifications then the ManagedForm can decide for itsels:
public OnFormActiveChanged(object sender, ...)
{
this.ShowNotifications = this.Form.IsActive;
}
If all red Forms should show the notifications:
public OnFormBackColorChanged(object sender, ...)
{
this.ShowNotifications = this.Form.BackColor == Color.Red;
}
If you have a lot of Forms, with only a few that show notifications, then a lot events OnShowNotification will be called for nothing, but since this is just a function call it won't be a problem unless you show 1000 forms or so, and I guess in that you have more serious problems.
Summerized
Decide the criterium on which a ManagedForm should show the notifications
Decide when a different form should show the notifications
Create an event handler for when the form changes, let the event handler set property ShowNotification
When the event to show the notification occurs, check the property.
Subscriptions are useful if you actually want these events to propagate to each form, but that doesn't seem like what you want to do. Given any action, your code is needing to show only one dialog box and update the status text of the main form.
Maybe you should consider using a singleton pattern, instead. By using a static event handler, this is essentially what you are already doing.
public class MainAppForm : Form
{
static MainAppForm mainAppForm;
public MainAppForm()
{
mainAppForm = this;
}
public static void NotifyOk(Form sender, string fullMessage = "Ok.", string shortMessage = null)
{
mainAppForm.NotifyOk(sender, fullMessage, shortMessage);
}
public void NotifyOk(Form sender, string fullMessage, string shortMessage)
{
this.statusStrip.Invoke(delegate {
this.statusStrip.Text = shortMessage;
});
}
}
I have a WPF/C# program with several classes, and the MainWindow.cs class has user controls which I'd like to update with the status of the computation occurring inside other classes. After googling around and borrowing from examples, I figured out how to set up an Event inside the other class, and invoking it when something changed. Then as long as the main class has a handler tied that event, I could appropriately update UI stuff (status bars, etc). Below is a stripped-down version of what I'm doing:
namespace Program
{
public partial class MainWindow : Window
{
public void SetUpHandler()
{
TestA.WorkerProgressThingie += new ProgressChangedEventHandler(TestA_ProgressChanged);
}
void TestA_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage
}
}
public class TestA
{
public static event ProgressChangedEventHandler WorkerProgressThingie;
public static void SomeFunction()
{
int value = 0;
//...(some boring code that does something here)
ProgressChangedEventArgs e = new ProgressChangedEventArgs(value, null);
if (WorkerProgressThingie != null)
WorkerProgressThingie.Invoke(null, e)
}
}
}
Is there not a way to simply call the progressBar property from the other class? (i.e. MainWindow.progressBar.Value)?
What is the purpose of the "object sender" parameter when I invoke the event, and how is it supposed to be used normally? The examples I see always use 'null'.
Thanks!
1) Yes, you can access any part of any class if it is declared public. In this case, you could declare the progressBar control as public, and anything that has a reference to class MainWindow can fiddle with it. HOWEVER, this would be pretty poor practice. Instead, you could bind to some 'value' which updates in relation to the current progress of the activity and let the MainWindow class worry about how it represents that change (in this case by updating a ProgressBar),
2) object sender in all events is meant to be a reference to the object which raised the event, so the event consumer knows where the event came from. Using null is also poor practice IMO, and in general, an object which raises an event should do so like;
SomeEvent(this, someEventArgs);
I need to "send" a piece of code from another thread (Excel Interop) to the UI thread to execute. Normally you'd use Invoke on the Form, which implements the ISynchronizeInvoke interface:
public class MyForm : Form
{
...
private void Button_Click(object sender, EventArgs e)
{
SomeExcelWorkbook.OnBeforeClose += delegate(ref bool Cancel)
{
this.Invoke(someCode);
};
}
}
Unfortunately there is a layer of abstraction between the form code and the code that defines the event handler, and I don't have a reference to the form at that point:
public void CodeExecutedByUIThread()
{
ISynchronizeInvoke sync;
SomeExcelWorkbook.OnBeforeClose += delegate(ref bool Cancel)
{
sync.Invoke(someCode);
};
}
When entering CodeExecutedByUIThread, we are still in the UI thread, so in theory everything needed should be there. Unfortunately the Form is the only class implementing that interface, is it?
How can I get an ISynchronizeInvoke from within the UI thread? Apparently Thread.CurrentThread doesn't provide it...
Or is there a way to get a SynchronizationContext? How would I retrieve and use that?
Update: Oh, I see, getting the SynchronizationContext object looks as simple as SynchronizationContext.Current, which doesn't need any reference to a form. So I'll google little bit more about how to use that.
In general, I think that neither is possible.
This might seem like an obvious answer, but what we used in this case was to store both the SynchronizationContext and the reference to the Form (as an ISynchronizeInvoke) in a static class at application startup.
MainForm main = new MainForm();
EnvironmentService.UI = main;
EnvironmentService.UIContext = SynchronizationContext.Current;
Application.Run(main);