How do I add one event to all textboxes? - c#

I made this physics calculator where I type in the acceleration and mass, for example, and it gives me the force. Now I came across 2 problems:
1) The current event is a mouse click, but I think having the answer pop up in a TextChanged event would be better. The thing is, I have around 9 textboxes and I don't think I want to add the same huge if statement to every single textbox event. How do I add this huge pile of code into every textbox without having to add an event to every single one?
2) My current method of doing things is checking if each textbox is null a number of times for each "formula". Which means I have the same formula getting repeated 3 times just because it gets written differently
For example:
F = ma
could be written as F/m = a which takes in another else if statement till it fills all the formulas. Is there a way I could make it briefer than what it currently is? Because right now I have to right this in front of every if statement:
else if (!String.IsNullOrEmpty(txtBoxAcc.Text) &&
String.IsNullOrEmpty(txtBoxFg.Text) &&
String.IsNullOrEmpty(txtBoxFn.Text) &&
String.IsNullOrEmpty(txtBoxF.Text) &&
String.IsNullOrEmpty(txtBoxFk.Text) &&
!String.IsNullOrEmpty(txtBoxMass.Text) &&
String.IsNullOrEmpty(txtBoxUk.Text) &&
String.IsNullOrEmpty(txtBoxVi.Text) &&
String.IsNullOrEmpty(txtBoxVf.Text) &&
String.IsNullOrEmpty(txtBoxTime.Text) &&
String.IsNullOrEmpty(txtBoxD.Text) &&
String.IsNullOrEmpty(txtBoxFnet.Text))

You need to register the same method in the event handler of TextChanged in all text boxes as following:
private void textBox_TextChanged(object sender, EventArgs e)
{
}
txtBoxFg.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxFn.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxF.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxFk.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxMass.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxUk.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxVi.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxVf.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxTime.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxD.TextChanged += new System.EventHandler(this.textBox_TextChanged);
txtBoxFnet.TextChanged += new System.EventHandler(this.textBox_TextChanged);
Thanks

1) Avoid handling a similar event at different positions in your code. Since you only deal with text-boxes it's very straight forward to do all your calculations in a single text-changed event-handler.
Just link it to every text-changed-event unsing +=

Define an event handler to use more than once:
EventHandler handler = (s, e) => {
var textBox = s as TextBox;
if (textBox == null)
return;
// handle event
};
Simple way to get all text boxes, except that it won't search within container controls:
Controls
.OfType<TextBox>()
.ToList()
.ForEach(textBox => textBox.TextChanged += handler);
Or a little more complicated way to get at all controls in the hierarchy:
var visited = new HashSet<Control>();
// tricky way to have a recursive lambda
Action<Control, Action<Control>> visitRecursively = null;
visitRecursively = (control, visit) => {
// duplicate control test might be unnecessary
// but avoids infinite recursion or duplicate events
if (visited.Contains(control))
return;
visited.Add(control);
visit(control);
control
.Controls
.Cast<Control>()
.ToList()
.ForEach(subControl => visitRecursively(subControl, visit));
};
Action<Control> addMyHandler = control => {
TextBox textBox = control as TextBox;
if (textBox != null)
textBox.TextChanged += handler;
};
visitRecursively(this, addMyHandler);

Related

tying multiple events to one handler:a function with 2 object params

This is my handler function:
protected static void textChange(object sender,Label labe1, EventArgs e)
{
var text = sender as TextBox;
if (text.Text != "")
labe1.Visible = false;
else
labe1.Visible = true;
}
Im trying to do this:
this.textBox1.Click += new System.EventHandler(textChange);
for multiple textboxes.I have tried making both params as objects and then interpreting them as label/textbox inside the function using a variable,ive tried making both of them label/textbox correspondingly in the params declaration.The only way it worked was by having only one object parameter while I need 2.
Assuming you're trying to associate each text box with a different label, you'll need to write a method that constructs an EventHandler for the relevant label, e.g.
public EventHandler CreateVisibilityHandler(Label label)
{
return (sender, args) => label.Visible = ((TextBox) sender).Text == "";
}
Then you can use:
textBox1.Click += CreateVisibilityHandler(label1);
textBox2.Click += CreateVisibilityHandler(label2);
// etc

C# Event Handlers getting more and more

I'm writing a simple Chess-Game at the moment.
In my game there are "Chessfields" and "Options". A Chessfield is every field on the board, a option is every move-possibility of a Figure on the field.
So when i click a chessfield, for every option-field i bind a new event-handler.
Like so:
private void Chessfield_Click(object sender, MouseButtonEventArgs e)
{
// ... some other stuff
PlaceOptions();
// ... some other stuff
}
Where the function PlaceOptions() does this:
private void PlaceOptions(List<(int, int)> Options, int SourceX, int SourceY)
{
foreach ((int, int) option in Options)
{
// ... some other stuff
chessfield.MouseDown -= Chessfield_Click;
// should remove all existing handlers for that field
foreach (MouseButtonEventHandler optionClickHandler in _recentOptionClickHandlers)
{
chessfield.MouseDown -= optionClickHandler;
}
chessfield.MouseDown += (sender, e) => Option_Click(sender, e, chessfield, SourceX, SourceY);
_recentOptionClickHandlers.Add((sender, e) => Option_Click(sender, e, chessfield, SourceX, SourceY));
// ... some other stuff
}
}
_recentOptionClickHandlers is a global variable that stores every handler i added to any option-field:
private List<MouseButtonEventHandler> _recentOptionClickHandlers = new List<MouseButtonEventHandler>();
Now: every time i click on a chessfield, the Chessfield_Click() handler only gets called once.
But there comes the problem:
When i then click on a option-field (so a possible move of a figure), all recently clicked normal chessfields get moved to that field, because all the previous handlers are still active, but i allready deleted them by calling:
foreach (MouseButtonEventHandler optionClickHandler in _recentOptionClickHandlers)
{
chessfield.MouseDown -= optionClickHandler;
}
And the more i click any fields, the more event handlers are getting called (1st time: 1 handler; 2nd time: 2 handlers; 3rd time: 4 handlers; ...)
This problem really drives me crazy since 2 days now.
Thanks in advance
Can't test it now and can't post a comment either, so I'll reply here.
I think that the handler added to _recentOptionClickHandlers is not the same that you're registering for the MouseDown event, as you're creating a new delegate before adding it to your list.
You should try something like this:
EventHandler evt = (sender, e) => Option_Click(sender, e, chessfield, SourceX, SourceY);
chessfield.MouseDown += evt;
_recentOptionClickHandlers.Add(evt);

C# / WPF create new button array with multiple function

I have wrote a code with this description:
First button calls FuncPopup(); function, after that every popup dialog creates new button. New button Create FuncPopup();. Old buttons should have various behavior.
private void FuncPopup()
{
FuncMenu popup = new FuncMenu();
popup.ShowDialog();
if (popup.DialogResult.HasValue && popup.DialogResult.Value)
{
i++;
newBtn[i] = new Button();
FuncGird.Children.Add(newBtn[i]);
Grid.SetColumn(newBtn[i], i);
Grid.SetRow(newBtn[i], j);
newBtn[i].Click += (sender, e) => clicked(i);
}
}
void clicked(int g) {
if (g >= i)
{
FuncPopup();
}
else (g < i){
OtherFunction();
}
}
i is a global variable. I expect Old buttons run OtherFunction(); but they always run FuncPopup();.
That's because as you said i is global variable, and you attach the following handler:
newBtn[i].Click += (sender, e) => clicked(i);
And you increment i all the time. You might think that value of i is fixed at the moment you attach this handler but it's not so. i in clicked(i) is the same global variable, which increments with every call. So g always equals i in that handler, and so for all buttons FuncPopup is called.
Instead save i to local variable and use that:
int tmp = i;
newBtn[i].Click += (sender, e) => clicked(tmp);

How to correctly reset a custom event in C#?

I have this code:
private void loadGENIOFileToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog dlgFile = new OpenFileDialog();
dlgFile.InitialDirectory = Properties.Settings.Default.PreviousPath;
dlgFile.Title = "Select GENIO file";
dlgFile.Filter = "GENIO files (*.txt)|*.txt";
dlgFile.FilterIndex = 0;
dlgFile.Multiselect = false;
if (dlgFile.ShowDialog() == DialogResult.OK)
{
Properties.Settings.Default.PreviousPath = Path.GetDirectoryName(dlgFile.FileName);
DeleteView();
m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
{
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
m_oThreadServices.SetGenioFilePath(dlgFile.FileName);
m_oThreadServices.start();
}
}
But I am also trying to implement a MRU handler:
private void OnMruFile(int number, String filename)
{
if (File.Exists(filename))
{
Properties.Settings.Default.PreviousPath = Path.GetDirectoryName(filename);
DeleteView();
m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
{
mruMenu.SetFirstFile(number);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
m_oThreadServices.SetGenioFilePath(filename);
m_oThreadServices.start();
}
else
mruMenu.RemoveFile(number);
}
}
My m_oThreadServices.OnLoadingCompleted line of code seems to require that I use += and as a result, if I first load a file, it adds the first event handler. If I then go to use the MRU list to load a different file it ends up running two OnLoadingCompleted handlers.
I tried m_oThreadServices.OnLoadingCompleted = but it will not allow it. So what is the right way for me to intercept the event handler and not end up calling both sets of code? Am I going about it wrong?
Thank you.
You should make sure your event handlers are unsubscribed from the event source once the event is raised.
In order to do that, you have to modify a bit the anonymous handlers. For instance, this snippet:
m_oThreadServices.OnLoadingCompleted += (_sender, _e) =>
{
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
should be like this:
EventHandler onLoadingCompleted = null;
onLoadingCompleted = (_sender, _e) =>
{
m_oThreadServices.OnLoadingCompleted -= onLoadingCompleted;
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
};
m_oThreadServices.OnLoadingCompleted += onLoadingCompleted;
Same for the other.
The line
EventHandler onLoadingCompleted = null;
is needed to avoid using uninitialized variable compiler error here
m_oThreadServices.OnLoadingCompleted -= onLoadingCompleted;
You can remove a handler if it's a named function:
private void OnLoadingComplete_AddFile(_sender, _e)
{
mruMenu.AddFile(dlgFile.FileName);
m_sUITInfo.dbDatabase = m_oThreadServices.GetDatabase();
CreateView();
}
...
m_oThreadServices.OnLoadingCompleted += OnLoadingComplete_AddFile;
...
m_oThreadServices.OnLoadingCompleted -= OnLoadingComplete_AddFile;
Removing a handler that hasn't been added (or has already been removed) is a no-op, so you can just remove the "other" handler before you add one: this will ensure there is at most one handler.
So basically += is syntactic sugar for calling Combine on your event. Delegates are stored in an Invocation List, and the default behavior when an event is fired is for each delegate in the invocation list to get called in the order they were added. This is why you cannot simply set OnLoadingCompleted to one delegate with an = sign - an event stores a list of delegates, not one.
You could remove a delegate with -= (syntactic sugar for calling Remove). Perhaps you want to formally declare the previous delegate somewhere rather than passing it as a lambda. This would let you remove it when you are done with it.
There is no straightforward way of removing anonymous or unknown events from a handler. However, you can take a look at this forum posting on MSDN: https://social.msdn.microsoft.com/Forums/vstudio/en-US/45071852-3a61-4181-9a25-068a8698b8b6/how-do-i-determine-if-an-event-has-a-handler-already?forum=netfxbcl
There is some code and discussion about using reflection to remove delegates from your event handler.
It might be better though to understand exactly what you are wanting to accomplish. Perhaps there is a better way to get the end-result that you are looking for rather than rewire events.
It isn't usually good practice to remove established event code to change the behavior of the code you want to implement. It can lead to unintended consequences, and erratic behavior. If event code is defined, it is almost always best to keep it in place and design your application around it.
On the other hand, if this is code that is added by you, or in your code-base, you can remove it, if you have done the proper research to validate its removal and not cause the application to break elsewhere. The best way to do that would be to have the event code in a named function:
public void MyEventCode(object sender, EventArgs args)
{
// Do event stuff..
}
Then you can remove the event by name:
control.DoMyEvent -= MyEventCode;

Is it possible to "steal" an event handler from one control and give it to another?

I want do something like this:
Button btn1 = new Button();
btn1.Click += new EventHandler(btn1_Click);
Button btn2 = new Button();
// Take whatever event got assigned to btn1 and assign it to btn2.
btn2.Click += btn1.Click; // The compiler says no...
Where btn1_Click is already defined in the class:
void btn1_Click(object sender, EventArgs e)
{
//
}
This won't compile, of course ("The event 'System.Windows.Forms.Control.Click' can only appear on the left hand side of += or -="). Is there a way to take the event handler from one control and assign it to another at runtime? If that's not possible, is duplicating the event handler and assigning it to another control at runtime doable?
A couple of points: I have googled the heck out of this one for awhile and found no way of doing it yet. Most of the attempted approaches involve reflection, so if you read my question and think the answer is incredibly obvious, please try to compile the code in Visual Studio first. Or if the answer really is incredibly obvious, please feel free to slap me with it. Thanks, I'm really looking forward to seeing if this is possible.
I know I could just do this:
btn2.Click += new EventHandler(btn1_Click);
That's not what I'm looking for here.
This is also not what I'm looking for:
EventHandler handy = new EventHandler(btn1_Click);
Button btn1 = new Button();
btn1.Click += handy;
Button btn2 = new Button();
btn2.Click += handy;
Yeah, it's technically possible. Reflection is required because many of the members are private and internal. Start a new Windows Forms project and add two buttons. Then:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Reflection;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
button1.Click += new EventHandler(button1_Click);
// Get secret click event key
FieldInfo eventClick = typeof(Control).GetField("EventClick", BindingFlags.NonPublic | BindingFlags.Static);
object secret = eventClick.GetValue(null);
// Retrieve the click event
PropertyInfo eventsProp = typeof(Component).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
EventHandlerList events = (EventHandlerList)eventsProp.GetValue(button1, null);
Delegate click = events[secret];
// Remove it from button1, add it to button2
events.RemoveHandler(secret, click);
events = (EventHandlerList)eventsProp.GetValue(button2, null);
events.AddHandler(secret, click);
}
void button1_Click(object sender, EventArgs e) {
MessageBox.Show("Yada");
}
}
}
If this convinces you that Microsoft tried really hard to prevent your from doing this, you understood the code.
No, you can't do this. The reason is encapsulation - events are just subscribe/unsubscribe, i.e. they don't let you "peek inside" to see what handlers are already subscribed.
What you could do is derive from Button, and create a public method which calls OnClick. Then you just need to make btn1 an instance of that class, and subscribe a handler to btn2 which calls btn1.RaiseClickEvent() or whatever you call the method.
I'm not sure I'd really recommend it though. What are you actually trying to do? What's the bigger picture?
EDIT: I see you've accepted the version which fetches the current set of events with reflection, but in case you're interested in the alternative which calls the OnXXX handler in the original control, I've got a sample here. I originally copied all events, but that leads to some very odd effects indeed. Note that this version means that if anyone subscribes to an event in the original button after calling CopyEvents, it's still "hooked up" - i.e. it doesn't really matter when you associate the two.
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
class Test
{
static void Main()
{
TextBox output = new TextBox
{
Multiline = true,
Height = 350,
Width = 200,
Location = new Point (5, 15)
};
Button original = new Button
{
Text = "Original",
Location = new Point (210, 15)
};
original.Click += Log(output, "Click!");
original.MouseEnter += Log(output, "MouseEnter");
original.MouseLeave += Log(output, "MouseLeave");
Button copyCat = new Button
{
Text = "CopyCat",
Location = new Point (210, 50)
};
CopyEvents(original, copyCat, "Click", "MouseEnter", "MouseLeave");
Form form = new Form
{
Width = 400,
Height = 420,
Controls = { output, original, copyCat }
};
Application.Run(form);
}
private static void CopyEvents(object source, object target, params string[] events)
{
Type sourceType = source.GetType();
Type targetType = target.GetType();
MethodInfo invoker = typeof(MethodAndSource).GetMethod("Invoke");
foreach (String eventName in events)
{
EventInfo sourceEvent = sourceType.GetEvent(eventName);
if (sourceEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", sourceType.Name, eventName);
continue;
}
// Note: we currently assume that all events are compatible with
// EventHandler. This method could do with more error checks...
MethodInfo raiseMethod = sourceType.GetMethod("On"+sourceEvent.Name,
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic);
if (raiseMethod == null)
{
Console.WriteLine("Can't find {0}.On{1}", sourceType.Name, sourceEvent.Name);
continue;
}
EventInfo targetEvent = targetType.GetEvent(sourceEvent.Name);
if (targetEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", targetType.Name, sourceEvent.Name);
continue;
}
MethodAndSource methodAndSource = new MethodAndSource(raiseMethod, source);
Delegate handler = Delegate.CreateDelegate(sourceEvent.EventHandlerType,
methodAndSource,
invoker);
targetEvent.AddEventHandler(target, handler);
}
}
private static EventHandler Log(TextBox output, string text)
{
return (sender, args) => output.Text += text + "\r\n";
}
private class MethodAndSource
{
private readonly MethodInfo method;
private readonly object source;
internal MethodAndSource(MethodInfo method, object source)
{
this.method = method;
this.source = source;
}
public void Invoke(object sender, EventArgs args)
{
method.Invoke(source, new object[] { args });
}
}
}
I did some digging around with #nobugz's solution and came up with this generic version which could be used on most general-purpose objects.
What I found out is that events for, dare I say, automatic events actually are compiled with a backing delegate field of the same name:
So here's one for stealing event handlers for simpler objects:
class Program
{
static void Main(string[] args)
{
var d = new Dummy();
var d2 = new Dummy();
// Use anonymous methods without saving any references
d.MyEvents += (sender, e) => { Console.WriteLine("One!"); };
d.MyEvents += (sender, e) => { Console.WriteLine("Two!"); };
// Find the backing field and get its value
var theType = d.GetType();
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var backingField = theType.GetField("MyEvents", bindingFlags);
var backingDelegate = backingField.GetValue(d) as Delegate;
var handlers = backingDelegate.GetInvocationList();
// Bind the handlers to the second instance
foreach (var handler in handlers)
d2.MyEvents += handler as EventHandler;
// See if the handlers are fired
d2.DoRaiseEvent();
Console.ReadKey();
}
}
class Dummy
{
public event EventHandler MyEvents;
public void DoRaiseEvent() { MyEvents(this, new EventArgs()); }
}
Thought it might be useful to some.
But do note that the way events are wired in Windows Forms components is rather different. They are optimized so that multiple events doesn't take up a lot of memory just holding nulls. So it'll need a little more digging around, but #nobugz has already done that :-)
The article Delegates and events about combined delegates might help clarify a lot of points in answers.
You could use a common event handler for your buttons and your picture boxes (as per the comments on an earlier answer) and then use the 'sender' object to determine how to handle the event at runtime.

Categories