Understanding this Event Example - c#

After reading online tutorials regarding events , I think I almost have an idea of whats going on. I developed the following extremely simple code to trigger an event in case a value is greater than 5.I know the code is pretty useless but I am using it to get my point across. (Instead of a main I just used a button event to trigger the code.)
//declare the delegate
public delegate void MyDelegate(string str);
public class SomeClass
{
public event MyDelegate MyEventFromDelegate;
private int i;
public int I
{
get
{ return i; }
set
{
if (value > 5)
{
MyEventFromDelegate("Value Greater than 5");
i = 0;
}
else
{
i = value;
}
}
}
}
public partial class Form1 : Form
{
public Form1()
{ InitializeComponent(); }
public void Method_To_Call(String rx)
{ MessageBox.Show("This method will be called if greater than 5");}
private void button1_Click(object sender, EventArgs e)
{
SomeClass a = new SomeClass();
a.MyEventFromDelegate +=new MyDelegate(Method_To_Call);
a.I = 12;
}
}
The only concern I have here is when we want to raise an event with the statement
MyEventFromDelegate("Value Greater than 5");
What point is passing a parameters to the event is at this point if later (at button click event) we are actually going to assign it a function to call every time an event is triggered.

In your very simple example - there is no point, because SomeClass instance "a" is very short-lived, and because you are not using rx parameter passed to Method_To_Call.
Your form method button1_Click is connected to the button's Click event through a delegate. Button does not know what code will execute when it is clicked. All it has to do is to signal that is has been clicked. That signal is implemented using a delegate.
Your could have defined your delegate as having an integer parameter where the checked value is passed. Then although the event method would be invoked only when value is greater than 5, inside the event method you could do things differently depending on the actual value.
//declare the delegate
public delegate void MyDelegate(int aValue);
public class SomeClass
{
public event MyDelegate MyEventFromDelegate;
private int i;
public int I
{
get
{ return i; }
set
{
if (value > 5)
{
MyEventFromDelegate(value);
i = 0;
}
else
{
i = value;
}
}
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void Method_To_Call(int aValue)
{
MessageBox.Show("This method signals that value is greater than 5. Value=" + aValue.ToString());
}
private void button1_Click(object sender, EventArgs e)
{
SomeClass a = new SomeClass();
a.MyEventFromDelegate +=new MyDelegate(Method_To_Call);
a.I = 12;
}
}

Related

C# pass delegates to different forms

i have a WinForms app that consists of several forms.
What I'm trying to achieve is to pass an event handler from a second form, to a third one, but i cannot achieve that. i get a casting error which i can't figure out how to overcome.
i would appreciate the help:
code + further explanation below:
This is a rough image of what is supposed to happen:
Form1 can create several forms (it also holds the methods that i want to pass) - which i can pass successfully on sub form creation.
the problem starts when i create form3 from within form2: i try to pass the event handler, but i get Error CS0029/CS0030 (casting errors)
what am i doing wrong and how to fix it?
EDIT:
what needs to happen? -- Form3 needs to control (send back data) to a Gui control placed in Form1
Code:
Form1:
public delegate void sendMessageToConsoleDelegate(string value);
public sendMessageToConsoleDelegate sendMessageToConsoleCallback;
public delegate void SetPlaceHolderDelegate(TextBox tb);
public SetPlaceHolderDelegate SetPlaceHolderCallback;
private void SetPlaceHolder(TextBox tb)
{
if (!tb.InvokeRequired)
{
if (!tb.Focused)
{
if (string.IsNullOrWhiteSpace(tb.Text))
tb.Text = tb.Tag.ToString();
return;
}
if (tb.Text == tb.Tag.ToString())
tb.Text = "";
return;
}
SetPlaceHolderDelegate call = new SetPlaceHolderDelegate(SetPlaceHolder);
tb.BeginInvoke(call, tb);
}
private void SendMessageToConsole(string msg)
{
if (!textBoxConsole.InvokeRequired)
{
textBoxConsole.AppendText(msg);
return;
}
sendMessageToConsoleDelegate call = new sendMessageToConsoleDelegate(SendMessageToConsole);
textBoxConsole.BeginInvoke(call, msg);
}
private void AddNewDeviceForm()
{
frmAddDevice add_device = new frmAddDevice(devicesDBPath);
add_device.sendMessageToConsole += SendMessageToConsole;
add_device.Show();
}
private void StartEdit()
{
frmEditDBs editdb = new frmEditDBs(devicesDBPath, commandsDBPath);
editdb.sendMessageToConsole += SendMessageToConsole;
editdb.SetPlaceHolder += SetPlaceHolder;
editdb.Show();
}
Form2 (frmEditDBs)
public delegate void EventHandler_sendMessageToConsole(string msg);
public event EventHandler_sendMessageToConsole sendMessageToConsole = delegate { };
public delegate void EventHandler_SetPlaceHolder(TextBox tb);
public event EventHandler_SetPlaceHolder SetPlaceHolder = delegate { };
private void EditDevice()
{
frmAddDevice edit_device = new frmAddDevice(devicesDBpath, current_device);
edit_device.sendMessageToConsole += sendMessageToConsole; ****<== This is the issue (same for the placeholder)****
edit_device.Show();
}
i get error CS0029
how can i pass the same delegate to other sub forms (e.g. frmAddDevice)?
Your question is how to C# pass delegates to different forms so that you can (for example) sendMessageToConsole to your MainForm from the other forms. In your code you state that this is the problem:
// This is the issue (same for the placeholder)****
edit_device.sendMessageToConsole += sendMessageToConsole;
When I look at your code, in essence you are trying to implement your own version of an Event Pattern. One solution to your issue would be to use a standard event pattern. Then intellisense will recognize your custom event delegate in the standard way:
FIRST you need to make the delegate and the inherited EventArgs class outside of your MainForm class:
namespace pass_delegates
{
public partial class MainForm : Form
{
}
// Make sure these are outside of any other class.
public delegate void SendMessageToConsoleEventHandler(object sender, SendMessageToConsoleEventArgs e);
public class SendMessageToConsoleEventArgs : EventArgs
{
public string Message { get; }
public SendMessageToConsoleEventArgs(string message)
{
Message = message;
}
}
}
Your frmAddDevice (shown here in minimal format) declares the delegate using the event keyword. Your other form frmEditDBs does exactly the same thing.
public partial class frmAddDevice : Form
{
public event SendMessageToConsoleEventHandler SendMessageToConsole;
public frmAddDevice(string devicesDBpath)
{
InitializeComponent();
}
protected virtual void OnSendMessageToConsole(SendMessageToConsoleEventArgs e)
{
SendMessageToConsole?.Invoke(this, e);
}
// Clicking the button will call this as a test.
private void btnSendTestMessage_Click(object sender, EventArgs e)
{
OnSendMessageToConsole(new SendMessageToConsoleEventArgs("Message received from 'Add Device Form'"));
}
}
A button in the MainForm code creates a new frmAddDevice like this:
frmAddDevice frmAddDevice = null;
// This handler in the Main Form creates the frmAddDevice form
private void btnFrmAddDevice_Click(object sender, EventArgs e)
{
if (frmAddDevice == null)
{
frmAddDevice = new frmAddDevice(devicesDBpath: "Some path");
// This was the problem. Not anymore ****
frmAddDevice.SendMessageToConsole += outputMessageToConsole;
}
frmAddDevice.Show();
}
private void outputMessageToConsole(object sender, SendMessageToConsoleEventArgs e)
{
textBoxConsole.AppendText(e.Message + Environment.NewLine);
}
If you do these things, you will achieve the functionality of sendMessageToConsole that your code is attempting to do. Try it out by downloading my sample from GitHub.
I think the main concept you don't understand is that delegate is "same level" as class, enum, struct etc. You need to declare it in some shared scope to make it accessible in both forms.
namespace ConsoleApp6
{
public delegate void TestDelegate();
public class ClassA
{
public TestDelegate delegateA;
}
public class ClassB
{
public TestDelegate delegateB;
}
internal class Program
{
static void Main(string[] args)
{
TestDelegate del = () => { };
var classA = new ClassA()
{
delegateA = del,
};
var classB = new ClassB()
{
delegateB = classA.delegateA
};
}
}
}
Or, if you want to keep it inside of the form, you need reference it by a class name the same way you would do with a type.
namespace ConsoleApp6
{
public class ClassA
{
public delegate void TestDelegate();
public TestDelegate delegateA;
}
public class ClassB
{
public ClassA.TestDelegate delegateB;
}
internal class Program
{
static void Main(string[] args)
{
ClassA.TestDelegate del = () => { };
var classA = new ClassA()
{
delegateA = del,
};
var classB = new ClassB()
{
delegateB = classA.delegateA
};
}
}
}
As was described previously, your "delegates" should be declared generically at the namespace of your project, not within a specific class so they are visible throughout your app. To do so, maybe make a separate file in your project for "MyDelegates" and may look something like:
using System.Windows.Forms;
namespace WinHelp1
{
// Create your own delegates outside of your classes that need to be publicly
// visible within your app or even protected if so needed.
public delegate void EventHandler_SendMessageToConsole(string msg);
public delegate void EventHandler_SetPlaceHolder(TextBox tb);
}
Now, in your form 1 that you want to define WHAT to do, do so based on the signatures matching appropriately
using System.Windows.Forms;
namespace WinHelp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void DoThisForConsole(string msg)
{
// whatever to do with string
}
public void DoThisForTextBox(TextBox tb)
{
// whatever to do with textbox
}
private void Btn2_Click(object sender, System.EventArgs e)
{
var f2 = new Form2();
f2.SendMessageToConsole += DoThisForConsole;
f2.SetPlaceHolder += DoThisForTextBox;
f2.ShowDialog();
// OR, if using the PARAMETERIZED for pass-through to call
// when form2 calls form 3
var f2b = new Form2( DoThisForConsole, DoThisForTextBox );
f2b.ShowDialog();
}
private void Btn3_Click(object sender, System.EventArgs e)
{
var f3 = new Form3();
f3.SendMessageToConsole += DoThisForConsole;
f3.SetPlaceHolder += DoThisForTextBox;
f3.ShowDialog();
}
}
}
First, form3 since that will just have the direct event handlers, and you can invoke however within form 3
using System.Windows.Forms;
namespace WinHelp1
{
public partial class Form3 : Form
{
// now, for each form you want to USE them on...
public event EventHandler_SendMessageToConsole SendMessageToConsole;
public event EventHandler_SetPlaceHolder SetPlaceHolder;
public Form3()
{
InitializeComponent();
}
}
}
Now, in your form 2, is a bit different. Since you want to make available for form2 to call form3 with the same event handler, just add those event handlers as parameters to the constructor class. Then you can preserve them in that form, but at the same time, self-register them as in the var f2b = new Form2 of the second button click event. Then use those preserved values when form2 needs to call form3
using System.Windows.Forms;
namespace WinHelp1
{
public partial class Form2 : Form
{
// now, for each form you want to USE them on...
public event EventHandler_SendMessageToConsole SendMessageToConsole;
public event EventHandler_SetPlaceHolder SetPlaceHolder;
// now, for each form you want to USE them on...
public EventHandler_SendMessageToConsole passThroughForMessage;
public EventHandler_SetPlaceHolder passThroughForTextBox;
public Form2()
{
InitializeComponent();
}
public Form2(EventHandler_SendMessageToConsole forSendMsg, EventHandler_SetPlaceHolder forPlaceHolder ) : this()
{
// preserve into properties in-case you need to call form 3
passThroughForMessage = forSendMsg;
passThroughForTextBox = forPlaceHolder;
// and the constructor can auto-set for itself so IT can notify as well
if( forSendMsg != null )
SendMessageToConsole += forSendMsg;
if( forPlaceHolder != null )
SetPlaceHolder += forPlaceHolder;
}
private void Btn3_Click(object sender, System.EventArgs e)
{
var f3 = new Form3();
// and the constructor can auto-set for itself so IT can notify as well
if (passThroughForMessage != null)
f3.SendMessageToConsole += passThroughForMessage;
if (passThroughForTextBox != null)
f3.SetPlaceHolder += passThroughForTextBox;
f3.ShowDialog();
}
}
}
Remember, parameters can be practically anything, and you can have a variable stored in a property just like anything else... as long as it matches the respective type.
Then, from form3, either instance will invoke back to whatever the root instance method may be.

How to update progress bar from another class C#?

I have my progressbar in form1. and i have another class called process.cs
In the main form I have these two functions...
public void SetProgressMax(int max)
{
uiProgressBar.Value = 0;
uiProgressBar.Minimum = 0;
uiProgressBar.Maximum = max;
}
public void IncrementProgress()
{
uiProgressBar.Increment(1);
}
How can I call these functions from my process.cs class?
You're creating a "tightly coupled" solution which requires the process class to have a reference to the Form (I'll use Form1 in this example).
So in your process class, you need to create a variable to store the reference to the form, and allow a way to pass that reference in. One way is to use the constructor of the class:
public class process
{
private Form1 f1 = null;
public process(Form1 f1)
{
this.f1 = f1;
}
public void Foo()
{
if (f1 != null && !f1.IsDisposed)
{
f1.SetProgressMax(10);
f1.IncrementProgress();
f1.IncrementProgress();
f1.IncrementProgress();
}
}
}
Here's an example of creating the process class from within Form1 and passing the reference in:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
process p = new process(this);
p.Foo();
}
public void SetProgressMax(int max)
{
uiProgressBar.Value = 0;
uiProgressBar.Minimum = 0;
uiProgressBar.Maximum = max;
}
public void IncrementProgress()
{
uiProgressBar.Increment(1);
}
}
--- EDIT ---
Here's a boiled down version of the "loosely coupled" events approach (ignoring multi-threading issues for simplicity):
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
process p = new process();
p.Progress += p_Progress;
p.Foo();
}
void p_Progress(int value)
{
uiProgressBar.Value = value;
}
}
public class process
{
public delegate void dlgProgress(int value);
public event dlgProgress Progress;
public void Foo()
{
// ... some code ...
// calcuate the current progress position somehow:
int i = (int)((double)3 / (double)10 * (double)100); // 30% complete
// raise the event if there are subscribers:
if (Progress != null)
{
Progress(i);
}
}
}
Note that in this approach the process class has no reference to the form and has no idea what is being done with the progress value. It simply reports the progress and the subscriber (the form in this case) decides what to do with that information.

Update label through another class

I'm trying to call method Run in script1. Then from script1 call method alert or wait and try to update statusLabel in Form1. But this code has an error.
static Label status = this.Controls.Find("statusLabel", true).FirstOrDefault() as Label;
This code will only work in Form1. Because this return error in another classes. Maybe it is not correct and you know better solution.
P.S. I know how to solve this problem (see below "Not the best solution"), but the code will be ~ 10-30 new lines.
Project
Form1.cs
public partial class Form1 : Form
{
private void statusLabel_Click(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
script1.Run();
...
script30.Run();
}
}
function.cs (Frequently used functions)
public class function
{
static Label statusLabel = this.Controls.Find("statusLabel", true).FirstOrDefault() as Label;
static public void alert(string str)
{
statusLabel.Text = str;
}
static public void wait(int sec)
{
int i = 0;
while (i++ < sec)
{
statusLabel.Text = "Wait: " + (sec+1-i).ToString();
Thread.Sleep(1000);
}
}
}
script1.cs (script1,script2 ... it is about 30 mini programs)
public class script1
{
static public void Run()
{
function.alert("Script1 is running");
function.wait(5);
function.alert("Script1 is done");
}
}
Not the best solution
remove in function.cs
static Label status = this.Controls.Find("statusLabel", true).FirstOrDefault() as Label;
Form1.cs
public partial class Form1 : Form
{
private void Form1_Load(object sender, EventArgs e)
{
script1.Run(this.statusLabel);
}
}
function.cs
public class function
{
private Label _statusLabel;
public scriptClass(Label statusLabel)
{
_statusLabel = statusLabel;
}
}
script1.cs (script1,script2 ... it is about 30 mini programs)
public class script1
{
static public void Run(Label statusLabel)
{
function _function = new function(statusLabel);
}
}
The statusLabel object is owned by, and should be encapsulated and hidden by, the Form1 class. To ensure good decoupling of your classes, as well as proper data hiding, only the Form1 class should be directly accessing it. And it should (by default) be able to access it via a field named statusLabel (i.e. no need to call this.Controls.Find() (nor should that even work from the function class, since that class also is not the owner of the object, nor of a Controls property).
The correct way to do this is for the script1 class to expose a StatusText property, and an event that is raised when the property value changes. There are two canonical ways to implement the event:
Implement an event named StatusTextChanged
Implement the INotifyPropertyChanged interface
Note that in your example, #2 is not an option because you are using static classes to implement your scripts. IMHO this is inadvisable for a variety of reasons, but since #1 is a perfectly fine solution I won't belabor that point. :)
The first looks like this:
class script1
{
public static string StatusText { get; private set; }
public static event EventHandler StatusTextChanged;
static public void Run()
{
ChangeStatusText("Script1 is running");
function.wait(5);
ChangeStatusText("Script1 is done");
}
static void ChangeStatusText(string text)
{
StatusText = text;
EventHandler handler = StatusTextChanged;
if (handler != null)
{
handler(null, EventArgs.Empty);
}
}
}
Then in Form1:
public partial class Form1
{
private void Form1_Load(object sender, EventArgs e)
{
script1.StatusTextChanged += (sender1, e1) => statusLabel.Text = script1.Text;
script1.Run();
...
script30.StatusTextChanged += (sender1, e1) => statusLabel.Text = script30.Text;
script30.Run();
}
}
Note in the above, each scriptX class has to reimplement the event. You could instead make a base class that each of the scriptX classes inherits, and which contains the event in question. Then the Form1 class need only subscribe to the one base class event. It would also address, or at least minimize the hassle of, the issue of leaving event handlers subscribed to 30 different events.
Of course, in this case then the Form1 class won't know which script is updating the text, but maybe that doesn't matter in your case.
Also note that if you did make the scriptX classes non-static, you might then again run into the issue of having to subscribe multiple times. But that is much more easily handled, since it seems certain in that case you'd use a base class, and so it would be easy to generalize the "subscribe, run script, unsubscribe" logic into a helper method.

Avoiding null pointer exception in delegates

I'm using delegates
in my c# windows forms application project.Using that I'm trying to remove items in a list box. I'm getting this null pointer exception and can somebody suggest a way to avoid that?
Delegate
public delegate void OrderEventDelegate (Object sender, OrderEventArgs args);
OrderEventArgs class
public class OrderEventArgs
{
private String message;
public String Message
{
get { return message; }
set { message = value; }
}
private int tableNo;
public int TableNo
{
get { return tableNo; }
set { tableNo = value; }
}
}
Class 1
public partial class Class1 : Form
{
private event OrderEventDelegate readyEvent;
public Class1(HomeForm parent, int tableNo)
{
InitializeComponent();
readyEvent -= new OrderEventDelegate(parent.readyOrder);
}
public void button_click()
{
OrderEventArgs readyOrderArg = new OrderEventArgs();
readyOrderArg.TableNo = 1;
readyOrderArg.Message = "123";
readyEvent(this, readyOrderArg);
}
}
Here readyEvent -= new OrderEventDelegate(parent.readyOrder);readyOrder() is the method which remove items in the list, which is located in the 'Homeform'.
Exception
It is possible to initialize C# events with an empty delegate. This way it can always be called safely without a null pointer check. As shown in this answer: https://stackoverflow.com/a/340618/2404788.
public delegate void OrderEventDelegate (Object sender, OrderEventArgs args) = delegate {};
If there's a possibility of something being null and you can do something about it/don't want to critically fail when it is, then check for it:
if (readyEvent != null) {
readyEvent( ... );
}
But the point here, I suppose, is that you don't want this thing to be null; so you should subscribe a handler to the event. I'm not sure why you're trying to remove a new instance of the delegate handler, but to add one you would use +=.

Why is subscribed event always null?

I declare a subscription to event in:
public class MainClass
{
public void btndel_bar_Click(object sender, RoutedEventArgs e)
{
SomeClass sc = new SomeClass();
sc.FieldUpdate += new SomeClass.FieldUpdateHandler(sc_FieldUpdate);
}
void sc_FieldUpdate(object sender, ValueEventArgs e)
{
MessageBox.Show(e.Smth_property);
}
}
And here is I want to listen event:
public class Someclass
{
public delegate void FieldUpdateHandler(object sender, ValueEventArgs e);
public event FieldUpdateHandler FieldUpdate;
void Somemethod()
{
string str = "Steel";
ValueEventArgs args = new ValueEventArgs(str);
FieldUpdate(this, args);
}
}
A class which carries data:
public class ValueEventArgs : EventArgs
{
private string smth;
public ValueEventArgs(string smth)
{
this.smth = smth;
}
public string Smth_property
{
get { return smth; }
}
}
I always have FieldUpdate=null. How to solve it?
You're calling Somemethod() in the constructor, before the calling code gets a chance to add the event handler.
Therefore, the event is still null.
The moment you create the object of SomeClass your event would get reinitialized.
Make your event a static so that multiple objects of SomeClass would share it
public static event FieldUpdateHandler FieldUpdate;
I've read articles about delegates and events and after reading I always I thought to make all operations again. I did all over again and it works! Consequently I done something wrong when I did at the beginning of.

Categories

Resources