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
Related
I have 2 buttons on a xamarin form,
scannerButton and checkOrderButton
scannerButton opens the scanner page, scans a QRCode and populates it into a order entry field
checkOrderButton reads whatever is in the order entry field and processes validations and sends it to server for verification
what I want - is to call the checkOrderButton.Click from within the scannerButton.Click - after it has scanned the text
code:
private async void scanCameraButton_Clicked(object sender, EventArgs e)
{
var options = new ZXing.Mobile.MobileBarcodeScanningOptions();
options.PossibleFormats = new List<ZXing.BarcodeFormat>() {
ZXing.BarcodeFormat.QR_CODE,ZXing.BarcodeFormat.EAN_8, ZXing.BarcodeFormat.EAN_13
};
var scanPage = new ZXingScannerPage(options);
scanPage.OnScanResult += (result) =>
{
//stop scan
scanPage.IsScanning = false;
Device.BeginInvokeOnMainThread(() =>
{
//pop the page and get the result
Navigation.PopAsync();
orderNoEntry.Text = result.Text;
});
//invoke checkOrderButton.Click here
};
what would be the best approach to do this?
one alternate is to dump all the functionality from checkOrderButton.Click handler into a function and then call that function from both button clicks, but I'm interested in learning how I can invoke the click event programmatically
What I would do is having a viewmodel with a command that performs whatever logic would be done when pressing the button .
Then bind the Command property of the button to the command property in the ViewModel.
At this stage you will have a command that you can execute programmatically just as if you called "Button.Click()" if there will be such thing .
I wrote an extension method for this purposes. This not only works in Xamarin-Forms-projects but also in WPF projects.
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
#if !NETSTANDARD
using System.Windows.Controls;
#else
using Xamarin.Forms;
#endif
public static class ButtonExtensions
{
public static void PerformClick(this Button sourceButton)
{
// Check parameters
if (sourceButton == null)
throw new ArgumentNullException(nameof(sourceButton));
// 1.) Raise the Click-event
#if !NETSTANDARD
sourceButton.RaiseEvent(new RoutedEventArgs(System.Windows.Controls.Primitives.ButtonBase.ClickEvent));
#else
sourceButton.RaiseEventViaReflection(nameof(sourceButton.Clicked), EventArgs.Empty);
#endif
// 2.) Execute the command, if bound and can be executed
ICommand boundCommand = sourceButton.Command;
if (boundCommand != null)
{
object parameter = sourceButton.CommandParameter;
if (boundCommand.CanExecute(parameter) == true)
boundCommand.Execute(parameter);
}
}
#if NETSTD
private static void RaiseEventViaReflection<TEventArgs>(this object source, string eventName, TEventArgs eventArgs) where TEventArgs : EventArgs
{
var eventDelegate = (MulticastDelegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source);
if (eventDelegate == null)
return;
foreach (var handler in eventDelegate.GetInvocationList())
{
#if !(NETSTANDARD1_6 || NETSTANDARD1_5 || NETSTANDARD1_4 || NETSTANDARD1_3 || NETSTANDARD1_2 || NETSTANDARD1_1 || NETSTANDARD1_0)
handler.Method?.Invoke(handler.Target, new object[] { source, eventArgs });
#else
handler.GetMethodInfo()?.Invoke(handler.Target, new object[] { source, eventArgs });
#endif
}
}
#endif
}
In fact you should pass two parameters, so it's not just supposed to be Button_Click(), you should call it like this: Button_Click(null, null) since it needs sender and e as two required parameters, take a look at the method definition: Button_Click(object sender, EventArgs e){..}
As Sami already mentions I would suggest to extract the functionality of the checkOrderButton.Click to a separate method so you can call that some method from the checkOrderButton.Click as wel as the scannerButton.Click.
In a method, I populate a list of IDs and pass them to my Background Worker. When I try to access this object, I'm getting a null value and I don't really know why.
public void Submit()
{
if (DataModels.Count == 0)
return;
List<long> docRefIds = new List<long>();
foreach (var row in DataModels)
{
if (row.PrintAck == true)
docRefIds.Add(Convert.ToInt32(row.DOCUMENT_REF_ID));
}
BackgroundWorker worker_Acknowledge = new BackgroundWorker();
worker_Acknowledge.DoWork += new DoWorkEventHandler(worker_Acknowledge_DoWork);
worker_Acknowledge.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(worker_Acknowledge_RunWorkerCompleted);
worker_Acknowledge.RunWorkerAsync(docRefIds.ToArray());
}
Then in the DoWork event...
private void worker_Acknowledge_DoWork(object sender, DoWorkEventArgs e)
{
var wkService = WorkItem.Services.Get<MyService>();
if (wkService == null)
return;
long?[] ids = e.Argument as long?[];
string errorMessage;
e.Result = wkService.SaveDetails(ids, out errorMessage);
}
I've set a breakpoint on the last line of my Submit method, and docRefIds has the valid data I'd expect. In fact, e.Argument has the 4 values but then ids is null after that line executes.
Of course after exhausting my resources and finally posting here, I realize my mistake.
My list wasn't nullable, so the cast in the DoWork event wasn't working. In my Submit method, the list should be declared as such:
List<long?> docRefIds = new List<long?>();
The name of the question is: "Updating the GUI from background worker", but the correct name world be: "Updating the GUI from background worker OR reporting multiple-variables (other than an integer) from background worker"
Please let me explain my situation. In a program I have a background worker which analyses the information.As the result of this analysis - form GUI elements should be populated with necessary data. In GUI I would like to update
2 datagridviews
1 listbox
5 labels
As I understand - I can only natively report 1 int value via ReportProgress() method of background worker.
So the question is - how can I pass a List<> ( + some other variables: string, int) via ReportProgress()? Basically - i want to update the GUI with the information but "1 integer" just won't do.. So either it should be possible to pass multiple variables via an ReportProgress() OR i can use an Invoke from inside the BackgroundWorker itself to update the GUI.. Personally I don't like the Invoke approach... What's your opinion?
Here is my code (see the comments):
private void button9_Click(object sender, EventArgs e) // start BW
{
bw.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
bw.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.RunWorkerAsync(10);
}
private void button10_Click(object sender, EventArgs e) // cancel BW
{
bw.CancelAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int count = (int)e.Argument;
for (int i = 1; i <= count; i++)
{
if (bw.CancellationPending)
{
e.Cancel = true;
break;
}
List<List<string>> list_result = new List<List<string>>();
list_result = Proccess();
bw.ReportProgress(list_result.Count()); // right now I can only return a single INT
/////////// UPDATE GUI //////////////
// change datagridview 1 based on "list_result" values
// change datagridview 2
// change listbox
// change label 1
// change label ..
Thread.Sleep(20000);
}
MessageBox.Show("Complete!");
e.Result = sum;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
prog_count++;
listBox1.Items.Add("Count: (" + prog_count.ToString() + "/20). Found: " + e.ProgressPercentage.ToString() + ".");
}
There's a UserState parameter when calling ReportProgress.
var list_result = new List<List<string>>();
new backgroundWorker1.ReportProgress(0, list_result);
The parameter type is an object so you'll have to cast it back to the type you need:
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var userState = (List<List<string>>)e.UserState;
}
The tricky issue with this is, how do you determine whether you're passing back a List, or a list of lists, or a single string, number, etc. You'll have to test for each possibility in the ProgressChanged event.
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var myList = e.UserState as List<List<string>>;
if (myList != null)
{
// use list
return;
}
int myNumber;
if (Int32.TryParse(e.UserState.ToString(), out myNumber))
{
// use number
return;
}
var myString = e.UserState.ToString();
// use string
}
Alternatively, you could create a class that holds all the values you need (or use Tuple), run everything in the background to populate that class, then pass that to the RunWorkerCompleted event, and update your UI all at once from there.
I have written two very easy methods that enable you to invoke your code (only if required) and you only need to write your code once. I think this makes Invoke much friendlier to use:
1) BeginInvoke
public static void SafeBeginInvoke(System.Windows.Forms.Control control, System.Action action)
{
if (control.InvokeRequired)
control.BeginInvoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
else
action();
}
2) Invoke
public static void SafeInvoke(System.Windows.Forms.Control control, System.Action action)
{
if (control.InvokeRequired)
control.Invoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
else
action();
}
It can be called like this:
SafeInvoke(textbox, () => { textbox.Text = "text got changed"; });
Alternatively you could just
System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false;
(which only changes behaviour in debug mode btw) and look if you run into problems.More often than not you actually don't. It took me quite some time to find cases very Invoke is really required for things not to get messed up.
The basic pattern for updating the UI from another thread is:
If controlItem.InvokeRequired Then
controlItem.Invoke(Sub() controlItem.Text = textUpdateValue)
Else
controlItem.Text = textUpdateValue
End If
This could update your list of controls without requiring you to pass anything through ReportProgress. If you would like to update your control from within the thread, I don't believe you need to check InvokeRequired, because it will always be required. However, best practices might be to expose the setting of a control via a property and then to do the full check so you can call it from anywhere.
I am trying to create a multi-client / server chat small application using WPF but I have some problems. Unfortunately when I press the Connect button the program crashes.
Well, I done that so far with the client program(with the thread):
public delegate void UpdateText(object txt);
I got that method:
private void UpdateTextBox(object txt)
{
if (msg_log.Dispatcher.CheckAccess())
{
Dispatcher.Invoke(new UpdateText(UpdateTextBox),txt);
}
else
{
msg_log.Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
}
And I am using a Button_Click event to connect to the server like that:
private void connect_Click(object sender, RoutedEventArgs e)
{
if ((nick_name.Text == "") || (ip_addr.Text == "") || (port.Text == ""))
{
MessageBox.Show("Nick name, IP Address and Port fields cannot be null.");
}
else
{
client = new Client();
client.ClientName = nick_name.Text;
client.ServerIp = ip_addr.Text;
client.ServerPort = port.Text;
Thread changer = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(UpdateTextBox));
changer.Start();
client.OnClientConnected += new OnClientConnectedDelegate(client_OnClientConnected);
client.OnClientConnecting += new OnClientConnectingDelegate(client_OnClientConnecting);
client.OnClientDisconnected += new OnClientDisconnectedDelegate(client_OnClientDisconnected);
client.OnClientError += new OnClientErrorDelegate(client_OnClientError);
client.OnClientFileSending += new OnClientFileSendingDelegate(client_OnClientFileSending);
client.OnDataReceived += new OnClientReceivedDelegate(client_OnDataReceived);
client.Connect();
}
}
Please note that the OnClient* events are like private void client_OnDataReceived(object Sender, ClientReceivedArguments R) { UpdateTextBox(R.ReceivedData); }
So these events should write some text like "Connected" into the msg_log TextBox
PS. The txt object used to be a string variable but I change it since ParameterizedThreadStart only accepts objects as parameters as I know.
Any help would be greatly appreciated!
Thanks in advance,
George
EDIT: Edited the UpdateTextBox method as Abe Heidebrecht suggested.
There are a few things wrong with your Invoke calls.
You don't need to create an object array to pass the parameters.
Passing DispatcherPriority.Normal is redundant (Normal is default).
You aren't passing any parameters to the second Invoke method (which is probably where your error is happening).
It should look like this:
private void UpdateTextBox(object txt)
{
if (msg_log.Dispatcher.CheckAccess())
{
Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
else
{
msg_log.Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
}
EDIT For StackOverflowException
This will cause a StackOverflowException because you are calling your method in an infinite loop. This is happening because your method is simply calling itself over and over again.
What Dispatcher.Invoke does is invoke the passed delegate on the thread that owns the Dispatcher. Just because the msg_log may have a different dispatcher, when you were calling UpdateTextBox, you were passing a delegate to the current method, which causes the infinite loop.
What you really need to do is call a method on the msg_log object, like so:
private void UpdateTextBox(object txt)
{
if (msg_log.Dispatcher.CheckAccess())
{
if (txt != null)
msg_log.Text = txt.ToString();
}
else
{
msg_log.Dispatcher.Invoke(new UpdateText(UpdateTextBox), txt);
}
}
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.