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’m using a separate class that instantiates different barcode objects depending on the type of machine, which raise events once a barcode is read.
So to transmit it to the form, I also raise an UNIQUE event ‘myEvent’ that all my forms listens, but inside the form I have to use this.Invoke((Action) mymethod(argument)); to be able to update the components at the user interface, as long as the handler to myEvent is executed on another thread different from that of the form.
So I have this:
My class -> barcode.readed(..) is triggered, then from its handler, I Raise myEvent, that is captured on my Form, and from the handler for myEvent on my form, I execute the this.Invoke…
I know this is a somewhat standard procedure, but I’d like to get rid of that Invoke, and instead, calling directly to the myMethod(argument); function.
I think that this is related to make my class thread safe, but I don’t know how to implement that in my case.
In fact, if I use the original manufacturer barcode.readed() event from inside the form it does not need to call the invoke, as long as this is thread safe, but I don’t know how to mimic that, and I really need to wrap all the different barcode handlers inside a different project, for my forms to use only one ‘myEvent’ that returns the desired barcode, and thus, not repeating code.
Thanks in advance,
Roger Tranchez
If you inherit your class from control (basically create a new custom control), you can handle events on the UI thread without using Invokes as the control (your barcode reader class) is part of the UI thread.
Sounds like you are using a worker thread for reading barcodes, to keep the UI responsive. And the barcode object just runs on whichever thread created it.
You can centralise event handling (to avoid repeating code) and read barcodes on a worker thread as follows:
Wrap the barcode object inside a custom object/library MyBarcodeReader that exposes MyEvent.
In the constructor for MyBarcodeReader, capture the current SynchronizationContext to a class field syncContext. This will be your UI's SynchronizationContext if your form constructs MyBarcodeReader.
When you activate MyBarcodeReader (e.g. MyBarcodeReader.Execute), create the barcode object on a worker thread.
When you need to raise MyEvent, call syncContext.Send (this will be on the worker thread), passing a delegate whose purpose is to raise MyEvent. syncContext.Send will synchronise to the UI thread (like Control.Invoke). The code below illustrates this.
public class MyBarcodeReader
{
private readonly SynchronizationContext syncContext;
// Handler for barcode object's Readed event.
private void Barcode.Readed(Object sender, Event e)
{
// Block the worker thread to synchronize with the thread associated
// with SynchronizationContext.
syncContext.Send(SyncMyEvent, (Object)e);
}
// Raises MyEvent on the thread associated with SynchronizationContext,
// usually a UI thread.
private void SyncMyEvent(Object o)
{
if (MyEvent != null)
{
MyEvent((Event)o);
}
}
// Constructor.
public MyBarcodeReader()
{
syncContext = SynchronizationContext.Current;
}
}
The approach here will block the worker thread (same as Control.Invoke) but not block the UI thread. If you have one or more forms that subscribe to MyEvent, they don't need to use Control.Invoke; they don't even need to know about the worker thread.
There are some excellent online references on SynchronizationContext, see CodeProject and MSDN magazine.
I've found a solution here: Basically it passes over the form control to the class constructor, and then, inside that class it uses the form.Invoke to fire the event from the form ui thread.
CLASS:
using System;
using System.Windows.Forms;
using System.Threading;
namespace ThreadTest
{
public class WorkerClass
{
private Thread thr;
// UI control for update
public Control UIControl { get; set; }
public delegate void StatusUpdate(DateTime dateTime, string message);
public event StatusUpdate OnStatusUpdate;
// Starts thread
public void Start()
{
thr = new Thread(new ThreadStart(MainWorker));
thr.Start();
}
// Main thread worker
public void MainWorker()
{
int i = 0;
while (true)
{
string message = string.Format("Value of i={0}", i++);
FireStatusUpdate(DateTime.Now, message);
Thread.Sleep(1000);
}
}
// Fire thread safe event
private void FireStatusUpdate(DateTime dateTime, string message)
{
// UIControl is set and OnStatusUpdate has subscriber
if (UIControl != null && OnStatusUpdate != null)
{
if (UIControl.InvokeRequired)
{
UIControl.Invoke(new StatusUpdate(FireStatusUpdate),
new object[] { dateTime, message });
return;
}
OnStatusUpdate(dateTime, message);
}
}
}
}
FORM:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace ThreadTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
WorkerClass worker = new WorkerClass();
// add event handler
worker.OnStatusUpdate += new WorkerClass.StatusUpdate(worker_OnStatusUpdate);
// add UI control to invoke
worker.UIControl = this;
worker.Start();
}
void worker_OnStatusUpdate(DateTime dateTime, string message)
{
label1.Text = dateTime.ToLongTimeString();
label1.Text += " " + message;
}
}
In my case, I've changed the type of event, from StatusUpdate to
EventHandler<MyEventArgs>
, being MyEventArgs this class:
public class MyEventArgs : EventArgs
{
public string MyString { get; set; }
}
Thank you !
Can anyone explain or show why my event handler doesn't update my Windows Form textbox? I have put the event handler in the UI thread to update a textbox in my GUI window. An EventLaunch method in my UI Thread #1 SetOperation class initiates an event. The UI Thread #1 SetOperation class, OnChDetDisplay event handler completes but the Window Form textbox doesn't update to the assigned value. What am I missing to tie the event and handler to updating the textbox?
Thanks for any help anyone can share,
Below is some code:
// Class runs in Thread #2: Prepares message data for Windows Form GUI display and passes to UI Thread #1
public class Aag_PrepDisplay
{
private Aag_PrepDisplay mAagPrep;
public Aag_PrepDisplay AagPrep
{
get { return mAagPrep; }
set { mAagPrep = value; }
}
// Thread #2: prepares message for Windows Form GUI display in UI Thread #1
public void PrepareDisplay(/*stuff*/)
{
mAagPrep = new Aag_PrepDisplay();
// does message prep stuff
SetOperation setOp1 = new SetOperation();
setOp1.FireEvent(mAagPrep); // call to UI Thread #1 method to fire event to update GUI; passes object with data
}
}
// UI Thread #1 class is the Windows Form. Displays and updates all textboxes.
public partial class SetOperation : Form
{
public event Action<object> OnChDet; // declared delegate object event that passes an object
public SetOperation()
{
InitializeComponent();
OnChDet += chDetDisplayHandler; // hooks handler to event
}
// Thread #1: accepts object w/data from Thread #2; Fires an event to update GUI Textbox(s)
private void FireEvent(Aag_PrepDisplay aagPrep)
{
OnChDet(aagPrep);
}
// UI Thread #1 event handler.
public void chDetDisplayHandler(object name)
{
// **** Problem: event is triggered and value assigned, but doesn't update the GUI window Textbox ********
actFreqChan1.Text = "402.5"; // this is only a test to see if event handler will update Textbox
// Next step: updateAll(name); // pass the object from Aag_PrepDisplay class
}
//Thread #1: update GUI Textbox values
public void updateAll(object name)
{
// this is where data from the Thread #2 AagPrep object will assign and update Textbox values
}
}
Put a breakpoint on the problem line and tell us what you see.
Probably it won't get called and the problem is upwards, in the event infrastructure.
If it gets called, the problem is in the Text field's setter.
In both cases, the defect is not where you think it is.
I'd simplify the code. Probably I'm missing something but I'm giving this a try.
public partial class SetOperation : Form
{
public event Action<object> OnChDet;
public SetOperation()
{
InitializeComponent();
OnChDet += chDetDisplayHandler;
}
private void chDetDisplayHandler(object name)
{
ActFreqChan1.Text = "402.5";
}
}
You can then fire the event simply with:
mySetOperationInstance.OnChDet(myNameObject);
The question is WHO will fire the event? This is up to you to find out.
You'll have to put the above line somewhere.
As far as I can tell, you don't need to have:
ChanEventArg;
ChDetHandler;
Aag_DisplayEvent (this looks completely wrong);
EventLaunch() method.
The only thing you should care about is:
having an event;
attaching a handler;
invoking it with parameters when need be.
Do this: make a backup copy of your code and try to simplify.
If it doesn't help I'm sorry, revert to your backup. Otherwise you have done way too much and somewhere you've lost your bearings about how exactly the event is dispatched.
Probably the event handler throws an Exception that doesn't emerge to the UI and remains latent. The following code will prevent other threads than the one creating the control from throwing exceptions:
The official reference: MSDN on InvokeRequired
Similar question: Using InvokeRequired vs control.InvokeRequired
The longer explanation but really good: MSDN tutorial on Thread-Safety in WinForms
Wrap the thread safety protection (InvokeRequired and so on) around this assignment inside the event handler:
actFreqChan1.Text = "402.5";
I hope this will help you out. Otherwise you can still come back here.
Thanks pid. I went back and recreated the code to update the Textbox, when the event is fired from within the SetOperation() of Thread 1. The event handler updates the Textbox. I then tried to call a Thread 1 method from PrepareDisplay() of Thread 2 and fire the event from the Thread 1 method. The event handler doesn't update the Textbox. Next, I added the safe thread-call code to Thread 1 SetOperation class. The Textbox doesn't update with the safe thread-call code. I took it right out of the MSDN tutorial. It was hard to follow the code flow when I stepped thru it. It jumped back and forth between methods. It appeared the InvokeRequired gave a false. In either case, the Textbox should be updated to 402.5. Do you see something that I misplaced or other missing code?
Below is the entire code that I simulated. Thanks again for your willingness to tutor me on some of this.
namespace TstTxtBoxUpdate
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Aag_PrepDisplay aag_Prep1 = new Aag_PrepDisplay();
Thread AagPrepDisplayThread = new Thread(new ThreadStart(aag_Prep1.PrepareDisplay));
AagPrepDisplayThread.Start();
while(!AagPrepDisplayThread.IsAlive)
;
Thread.Sleep(1000);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new SetOperation());
}
}
}
namespace TstTxtBoxUpdate
{
// Thread 1: UI
public partial class SetOperation : Form
{
private string text;
public event Action<object> OnChDet;
delegate void SetTextCallback(string text);
private Thread demoThread = null;
public SetOperation()
{
InitializeComponent();
OnChDet += chDetDisplayHandler;
}
public void FireEvent(Aag_PrepDisplay aagPrep)
{
OnChDet(mName);
}
private void chDetDisplayHandler(object name)
{
this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
private void ThreadProcSafe()
{
this.SetText("402.5");
}
private void SetText(string text)
{
if(this.actFreqChan1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
// TextBox NOT updated when event called from FireEvent() that was called from Thread 2 PrepareDisplay()
// TextBox IS updated when event called from Thread 1: SetOperation() or FireEvent()
this.actFreqChan1.Text = text;
}
}
}
}
namespace TstTxtBoxUpdate
{
// Thread 2: Data prepare
public class Aag_PrepDisplay
{
#region Fields
private Aag_PrepDisplay mAagPrep;
#endregion Fields
#region Properties
public Aag_PrepDisplay AagPrepDisp;
public Aag_PrepDisplay AagPrep
{
get { return mAagPrep; }
set { mAagPrep = value; }
}
#endregion Properties
#region Methods
public void PrepareDisplay()
{
mAagPrep = new Aag_PrepDisplay();
SetOperation setOp1 = new SetOperation();
setOp1.FireEvent(mAagPrep); // calls Thread 1 method that will fire the event
}
#endregion Methods
}
}
I have the class called mainForm that it is main window of my program. I create a TextBox(this TextBox Logs program) object in this class and i want to write program status to it. I do this from mainForm and other object(by passing TextBox object to it) easily, But when i want to do that from another thread, it's complicated.
However, i am writing to TextBox by the thread that it runs the defined code in mainForm(using delegate).
My question is, How to write in the TextBox from thread that runs in another class?
public partial class mainForm : Form
{
TextBox log = new TextBox();
.
.
.
OtherClass o = new OtherClass(log);
}
class OtherClass
{
private TextBox log;
public otherClass(TextBox aLog)
{
log = aLog;
Thread thread = new Thrad(new ThreadStart(this.run));
thread.Start();
}
private void run()
{
log.Text = "Message";// I Can't Do This. Can I Use Delegate Here? How?
}
}
You can use Invoke/BeginInvoke:
log.BeginInvoke(
(Action)(() =>
{
log.Text = "Message";
}));
This allows the secondary thread to safely forward GUI changes to the GUI thread which is the only one that should apply them.
Another way using defined delegate - incidently Xt here can be reused for other methods as long as the signature is the same. Parameters can also be passed - (would then have parameters in the Xt delegate and Invoke of it would pass a coma separated list for each.
private void run()
{
XteChangeText();
}
private delegate void Xt();
private void XteChangeText()
{
if (log.InvokeRequired)
{
Invoke(new Xt(XteChangeText));
}
else
{
log.Text="Message";
}
}