I have three forms named frmBase(form1),frmGraph(form2) and frmBalloon(form3).
frmGraph(form2) opens if the user clicks on the button named btnShow placed on frmBase(form1).
frmBalloon(form3) opens if the user clicks on the button named btnShow placed on frmGraph(form2).
Now if the user clicks on the button named btnCancel placed on the frmGraph(form2) OR clicks on the button named btnCancel placed on the frmBalloon(form3) every form that is open should be closed except frmBase(form1).
So, every forms should be closed except mainform when user clicks on the button placed on the form2 or form3. So for that is there any solution?
Maintain references of all form objects that needs to be closed on designated event. Create and Call the function in frmBase whenever needed. The function will be responsible to close all registered forms if open.
Looks like an observer pattern case.
It is not a great solution, Application.OpenForms is a bit unreliable, but easy:
public static void CloseAllFormsButMain() {
for (int ix = Application.OpenForms.Count - 1; ix > 0; --ix)
Application.OpenForms[ix].Close();
}
Observer pattern is appropriate with some specializaiton. A more specialized version of the observer pattern for this type of scenario is the EventAggregator pattern. The event aggregator pattern is ideal for this type of scenario.
In short the event aggregator allows you to centralize the publishing/subscription of events. Therefore all subscribers and publishers talk only to the EventAggregator. Subscribers subscribe to events and publishers command the event aggregator to publish something.
The event aggregator pattern also decouples each publisher subscriber from each other. This eliminates the need for the child forms to reference the parent forms.
Jeremy Miller provides a good example in his Build Your Own Cab series. Due to my new membership I cant post the links to the sites but just a do a search for the following items.
EventAggregator by Martin Fowler
Build Your Own CAB series by Jeremy Miller (codebetter.com)
EventAggregator in PRISM
Here is a simple example I cooked up using C# and generics. This is by no means complete. It is just to illustrate a simplified example. For a more complete patter turn to Jeremy Millers example.
[code]
//Sample Custom Event args
using System;
namespace EventAggregatorPatternDemo
{
public class CloseAllFormsEventArgs : EventArgs
{
}
}
//Sample Form Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace EventAggregatorPatternDemo
{
public partial class GraphForm : Form, IListener
{
public GraphForm()
{
InitializeComponent();
}
private void GraphForm_Load(object sender, EventArgs e)
{
EventAggregator.GetTheEventAggregator().Subscribe<CloseAllFormsEventArgs>(this);
}
public void Handle<TEventArgs>(TEventArgs e)
{
if (e.GetType() == typeof(CloseAllFormsEventArgs))
{
this.Close();
}
}
private void btnCloseAll_Click(object sender, EventArgs e)
{
EventAggregator.GetTheEventAggregator().Publish(this, new CloseAllFormsEventArgs());
}
private void GraphForm_FormClosed(object sender, FormClosedEventArgs e)
{
EventAggregator.GetTheEventAggregator().CancelSubscription<CloseAllFormsEventArgs>(this);
}
}
}
//Event Aggregator code
using System;
using System.Collections.Generic;
namespace EventAggregatorPatternDemo
{
public interface IListener
{
void Handle(TEventArgs e);
}
public class EventAggregator
{
static EventAggregator _TheEventAggregator;
readonly Dictionary<Type, List<IListener>> _listeners;
private EventAggregator()
{
_listeners = new Dictionary<Type, List<IListener>>();
}
public static EventAggregator GetTheEventAggregator()
{
if(_TheEventAggregator == null)
{
_TheEventAggregator = new EventAggregator();
}
return _TheEventAggregator;
}
public void Publish<TEventArgs>(object sender, TEventArgs e)
{
if(_listeners.ContainsKey(e.GetType()))
{
var listOfSubscribers = _listeners[e.GetType()];
for(int i = listOfSubscribers.Count - 1; i > -1; i--)
{
listOfSubscribers[i].Handle(e);
}
}
}
public void Subscribe<TEventArgs>(IListener listener)
{
if(_listeners.ContainsKey(typeof(TEventArgs)))
{
_listeners[typeof(TEventArgs)].Add(listener);
}
else
{
List<IListener> newListenerList = new List<IListener>();
newListenerList.Add(listener);
_listeners.Add(typeof(TEventArgs), newListenerList);
}
}
//Cancels all subscriptions
public void CancelSubscription<TEventArgs>(IListener listener)
{
Type eventArgsType = typeof(TEventArgs);
if (_listeners.ContainsKey(eventArgsType))
{
//Remove from the end
for (int i = _listeners[eventArgsType].Count-1; i > -1; i-- )
{
//If the objects are the same
if(ReferenceEquals(_listeners[eventArgsType][i], listener))
{
_listeners[eventArgsType].RemoveAt(i);
}
}
}
}
}
}
[/code]
Have you thought about using a static event? Here is a simple example.
Related
This question already has answers here:
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on
(22 answers)
Closed 3 years ago.
Apologies as I know cross-thread operations have been addressed elsewhere, but all I see are fragments and can't quite figure out where to apply the solutions correctly. So I have distilled this problem to it's most basic essence in the hope that me and anybody else who comes across this can see a complete solution without a lot of support code to wade through or fragments with limited context.
I have looked at various posts such as here, here and here but can't quite place the solutions in context.
I have created a Windows Forms project with a single button and textbox. In the form.cs file here is the complete code to demonstrate the problem:
using System;
using System.Windows.Forms;
using System.Threading;
namespace SampleEventTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Button1_Click(object sender, EventArgs e)
{
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
TestClass();
}).Start();
}
private void TestClass()
{
TestEvent tv = new TestEvent();
tv.OnClsConnect += new TestEvent.clsConnect(OnConnected);
tv.DoSomethingThread();
}
public void OnConnected(string str)
{
textBox1.Text = str;
}
}
public class TestEvent
{
public delegate void clsConnect(string str);
public event clsConnect OnClsConnect;
public void DoSomethingThread()
{
if (OnClsConnect != null)
{
OnClsConnect("Thread run");
}
}
}
}
Click the button and you will get the "Cross thread operation not valid" error. How does one properly fix this code? TIA
Basically you can use the BackgroundWorker Control. here is a quick link. also here is an anther link which helps you.
also please read this:
How to: Make thread-safe calls to Windows Forms controls
H/T to S.A. Parkhid for this link.
Here is the solution: modify the method OnConnected to the following:
public void OnConnected(string str)
{
if (textBox1.InvokeRequired)
{
var d = new TestEvent.clsConnect(OnConnected);
textBox1.Invoke(d, new object[] { str });
}
else
{
textBox1.Text = str;
}
}
I have a question about callback.
I have two forms(VS2010).
I have made a class that raise an event when the ''valueIN'' change.
I set a delegate in my class so that any Form can get the ValueIN when it changes.
The problem
I create the Form2 object and link a callback to it so it is able to get ''valueIN''. but if the form2 object is not instantiated the run time tells me that there is no instantiation of the object.
So I would like to know how do I know that Form2 exists in my WorkingStation.
This line: SetValueINValCallback(value_received);
should looks like something (In workstationlooking at Form2): if(SetValINCallbackFn.exists) SetValueINValCallback(value_received);
Form2
namespace DelegateTest
{
using Beckhoff.App.Ads.Core;
using Beckhoff.App.Ads.Core.Plc;
using TwinCAT.Ads;
using System.IO;
public delegate void DEL_SetValIN(Single value);//test event
public partial class Form1 : Form
{
IBAAdsServer _adsServer;
WorkingStation WorkStation;
public Form1()
{
InitializeComponent();
WorkingStation WorkStation = new WorkingStation(_adsServer);
}
private void btn_Frm2Open_Click(object sender, EventArgs e)
{
Form2 Form2Test = new Form2();
WorkStation.SetValueINValCallback += new DEL_SetValIN(Form2Test.SetValINCallbackFn);
Form2Test.Show();
}
}
}
namespace DelegateTest
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
public void SetValINCallbackFn(Single Value_received)//refresh valueIN
{
label1.Text = Value_received.ToString("F1");
}
}
}
WorkStation
namespace DelegateTest
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;//msgbox
using Beckhoff.App.Ads.Core;
using Beckhoff.App.Ads.Core.Plc;
using TwinCAT.Ads;
public class WorkingStation
{//working station class
public DEL_SetValIN SetValueINValCallback;
private static IBAAdsCncClient _cncClient;
private static IBAAdsPlcClient _plcClient;
public static List<IBAAdsNotification> _notifications;
//no need public event System.EventHandler e_RefreshValueIN;//Input value has changed
public WorkingStation(IBAAdsServer _adsServer) //constructor
{
try
{
_cncClient = _adsServer.GetAdsClient<IBAAdsCncClient>("CNC");
_plcClient = _adsServer.GetAdsClient<IBAAdsPlcClient>("PLC");
_notifications = new List<IBAAdsNotification>();
var _refreshValueIN = new OnChangeDeviceNotification("GlobalVar.PLC.valueInput", ValINHasChanged);//event handler value
_plcClient.AddPlcDeviceNotification(_refreshValueIN);
_notifications.Add(_refreshValueIN);
}
catch (Exception Except)
{ MessageBox.Show("Error init object:" + Except.Message); }
}
~WorkingStation()//destructor
{
foreach (var notify in _notifications)
{
_plcClient.RemovePlcDeviceNotification(notify);
}
}
protected virtual void ValINHasChanged(object sender, BAAdsNotificationItem notification)//get Input value
{//event method
Single value_received = 0;
try
{
value_received = _plcClient.ReadSymbol<Single>("valueInput");
SetValueINValCallback(value_received);
//no need EventHandler E_VIChange = e_RefreshValueIN;
//no need if (E_VIChange != null)
//no need E_VIChange(this, EventArgs.Empty);
}
catch (Exception except)
{
MessageBox.Show("bad event (valueIN): " + except.Message);
}
}
}
}
or does it exist another way to pass event from a class to multiple Forms?
I need those values to draw a chart in Form2 for an example.
Thanks in advance for your help!
Sorry because I didn't see that I was raising an event.
If I've made a delegate is to subscribe to the event, so I don't need to had a raising event, I just need to subscribe.
The error raised because if you code an event like:
protected virtual void ValINHasChanged(object sender, BAAdsNotificationItem notification)//get Input value
{//event method
{
EventHandler E_VIChange = e_RefreshValueIN;
if (E_VIChange != null)
E_VIChange(this, EventArgs.Empty);
}
You need to have a subscriber like
subscriber += new EventHandler... and so on
And you don^t need it anymore because you have the delegate that make the job. so you just have to subscribe to the delegate callback to have your info.
Sorry for the wasting of time.
Best regards
I'm building a game and trying to use an event system.
This is the main idea of how it is implemented:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Blocker2
{
public delegate void OnPlayerMoved(PlayerMovedEvent e);
public delegate void OnPlayerSpawned(PlayerSpawnedEvent e);
public delegate void OnBlockBreak(BlockBreakEvent e);
public delegate void OnBlockPlaced(BlockPlacedEvent e);
public static class EventHandler
{
private static List<OnPlayerMoved> _onPlayerMoved;
private static List<OnPlayerSpawned> _onPlayerSpawned;
private static List<OnBlockBreak> _onBlockBreak;
private static List<OnBlockPlaced> _onBlockPlaced;
static EventHandler()
{
}
public static void Subscribe()
{
}
// -------------------------- Player Related Events --------------------------
public static void OnPlayerMoved(PlayerMovedEvent e)
{
foreach (OnPlayerMoved del in _onPlayerMoved)
{
del(e);
}
}
public static void OnPlayerSpawned(PlayerSpawnedEvent e)
{
foreach (OnPlayerSpawned del in _onPlayerSpawned)
{
del(e);
}
}
// -------------------------- Block Related Events --------------------------
public static void OnBlockBreak(BlockBreakEvent e)
{
foreach (OnBlockBreak del in _onBlockBreak)
{
del(e);
}
}
public static void OnBlockPlaced(BlockPlacedEvent e)
{
foreach (OnBlockPlaced del in _onBlockPlaced)
{
del(e);
}
}
}
}
And there going to be alot more events, and I think this method going to make the code very very complex. There is a better way to do it? (Considering performance and maintability of the code).
Thanks in advanced!
Sorry for my bad english.
Why don't you use standard C# events? They will handle this the same way, since an event allows more than a single subscriber.
The standard event mechanism in C# allows multiple subscribers to subscribe to an event, and would look like:
public static event EventHandler<PlayerMovedEventArgs> PlayerMoved;
// Often, you'll have a method to raise the event:
public static void OnPlayerMoved(PlayerMovedEventArgs args)
{
var handler = PlayerMoved;
if (handler != null)
handler(null, args);
}
That being said, I would recommend putting these into the class where they are related, and not having them all global/static. You could then potentially make the method to raise the event private to that class, which would allow you to keep the design more maintainable.
For example, the PlayerMoved event probably would be more appropriate within some class representing your world (or a piece of the world), and in there in a non-static fashion.
How would you suggest the best way of avoiding duplicate event subscriptions? if this line of code executes in two places, the event will get ran twice. I'm trying to avoid 3rd party events from subscribing twice.
theOBject.TheEvent += RunMyCode;
In my delegate setter, I can effectively run this ...
theOBject.TheEvent -= RunMyCode;
theOBject.TheEvent += RunMyCode;
but is that the best way?
I think, the most efficient way, is to make your event a property and add concurrency locks to it as in this Example:
private EventHandler _theEvent;
private object _eventLock = new object();
public event EventHandler TheEvent
{
add
{
lock (_eventLock)
{
_theEvent -= value;
_theEvent += value;
}
}
remove
{
lock (_eventLock)
{
_theEvent -= value;
}
}
}
I have done this before....it assumes it is acceptable that the last subscriber is what gets called.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
MyObject my = new MyObject();
my.Changed += new EventHandler(my_Changed);
my.Changed += new EventHandler(my_Changed1);
my.Update();
Console.ReadLine();
}
static void my_Changed(object sender, EventArgs e)
{
Console.WriteLine("Hello");
}
static void my_Changed1(object sender, EventArgs e)
{
Console.WriteLine("Hello1");
}
}
public class MyObject
{
public MyObject()
{
}
private EventHandler ChangedEventHandler;
public event EventHandler Changed
{
add
{
ChangedEventHandler = value;
}
remove
{
ChangedEventHandler -= value;
}
}
public void Update()
{
OnChanged();
}
private void OnChanged()
{
if (ChangedEventHandler != null)
{
ChangedEventHandler(this, null);
}
}
}
}
Is your code multi threaded ? Concurrency lock is needed only when its multi threaded. If not its a overhead.
As such your approach of unsubscribing and subscribing is correct.
Thanks
If you own the source for the class of theObject, then you have access to the InvocationList of TheEvent. You can implement your own add accessor for the event and check before adding.
However, I think that your approach is fine too.
I use your approach except one detail. I think, that events should be subscribed when you create new instance of subscriber or theObject, this makes code more straight. Thus, all you need is just carefully watch after correct objects disposing (dispose patten is convenient solution for this).
You mentioned that you use 3rd party event, that means that you can't provide your own realisation for add/remove methods, as you have been advised. But in your own classes with your own events you should define your own realisation of add/remove methods for event in order to solve your problem.
How do I bind a ProgressBar to a property of a class updated in another thread?
The following code example shows my first naive attempt. It doesn't work because I get runtime errors about cross thread communication. I think I need to use Invoke in some way, but I'm not sure how to do it with the Binding class.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;
class ProgressForm : Form
{
private ProgressBar pbProgress;
public ProgressForm(ref LongOp lo)
{
Binding b = new Binding("Value", lo, "Progress");
pbProgress = new ProgressBar();
pbProgress.DataBindings.Add(b);
this.Controls.Add(pbProgress);
}
}
class Program : Form
{
private Button btnStart;
private LongOp lo;
public Program()
{
lo = new LongOp();
btnStart = new Button();
btnStart.Text = "Start long operation";
btnStart.Click += new EventHandler(btnStart_Click);
this.Controls.Add(btnStart);
}
private void btnStart_Click(object sender, EventArgs e)
{
ProgressForm pf = new ProgressForm(ref lo);
lo.DoLongOp();
pf.ShowDialog();
}
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Program());
}
}
class LongOp : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int progress;
public void DoLongOp()
{
Thread thread = new Thread(new ThreadStart(this.run));
thread.Start();
}
public void run()
{
for (int i = 0; i < 10; ++i)
{
Thread.Sleep(1000);
Progress++;
}
}
public int Progress
{
get
{
return progress;
}
set
{
progress = value;
NotifyPropertyChanged("Progress");
}
}
private void NotifyPropertyChanged(String field)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(field));
}
}
}
So how do I bind a ProgressBar to a value updated in another thread?
Thanks in advance
EDIT: I've switched to using the ThreadedBinding implementation Mr. Gravell wrote and linked to. I'm still getting the cross thread exception though. Pressing "Break" in the exception dialog highlights the PropertyChanged(this, new PropertyChangedEventArgs(field)); line as the line causing the exception.
What more do I need to change?
EDIT: Looks like Mr. Gravell's post has been removed. The ThreadedBinding implementation I mentioned can be found at the end of this thread: http://groups.google.com/group/microsoft.public.dotnet.languages.csharp/browse_thread/thread/69d671cd57a2c7ab/2f078656d6f1ee1f?pli=1
I've switched back to plain old Binding in the example for easier compilation by others.
Unfortunately I think the cross-threading issues will make data-binding proper a bit too clumsy to use here, and probably more complexity than you need in any case -- the data only needs to be plumbed one way.
You could just replace the binding with an event handler like this:
private void ProgressPropertyChangedHandler(object sender,
PropertyChangedEventArgs args)
{
// fetch property on event handler thread, stash copy in lambda closure
var progress = LongOp.Progress;
// now update the UI
pbProgress.Invoke(new Action(() => pbProgress.Value = progress));
}