How to pass argument to function triggered by Button in Gtk#? - c#

I'm trying to pass file in FileChooser in Gtk# and them using second button read from this file. I can't pass FileChooser to function, which is triggered when second button is clicked.
namespace SharpTest{
internal static class SharpTest{
public static void Main(string[] args){
Application.Init();
var window = new Window("Sharp Test");
window.Resize(600, 400);
window.DeleteEvent += Window_Delete;
var fileButton = new FileChooserButton("Choose file", FileChooserAction.Open);
var scanButton = new Button("Scan file");
scanButton.SetSizeRequest(100, 50);
scanButton.Clicked += ScanFile;
var boxMain = new VBox();
boxMain.PackStart(fileButton, false, false, 5);
boxMain.PackStart(scanButton, false, false, 100);
window.Add(boxMain);
window.ShowAll();
Application.Run();
}
private static void Window_Delete(object o, DeleteEventArgs args){
Application.Quit ();
args.RetVal = true;
}
private static void ScanFile(object sender, EventArgs eventArgs){
//Read from file
}
}
}

The FileName property in FileChooserButton scanButton holds the name of the file chosen. The problem you face is that you cannot access the button from ScanFile(), since it is outside Main(), and scanButton is a local reference inside it.
Also, you are using an old-style fashion of creating event handlers. You can actually use lambdas for that purpose (the easiest option), and modify the parameters in the call to ScanFile() the way you like.
So, instead of:
scanButton.Clicked += ScanFile;
you can change it for:
scanButton.Clicked += (obj, evt) => ScanFile( fileButton.Filename );
which will do the trick, provided that you change ScanFile() to be:
private static void ScanFile(string fn)
{
// ... do something with the file name in fn...
}
With that lambda, you are creating an anonymous function that accepts an object obj (the sender of the event), and an EventArgs args object (the arguments of the event). You are doing nothing with that info, so you dismiss it, since you're just interested in the value of the FileName property in scanButton.
Hope this helps.

Related

Add Click event to my dynamically created Xaml Button

I'm currently writing a function that creates button in XAML (on runtime) according to JSON information that is received from an API call in C#.
I currently have the following code:
Button downloadButton = new Button();
downloadButton.Click += new RoutedEventHandler(DownloadContent);
But I get the following error:
An object reference is required for the non-static field, method, or
property 'Mainwindow.DownloadContent(object, RoutedEventArgs)'
I've tried changing it to downloadButton.Click += new RoutedEventHandler(this, DownloadContent); but that gives me the error:
Keyword 'this' is not valid in a static property, static method, or static field initializer
For reference this is what DownloadContent looks like
private void DownloadContent(object sender, RoutedEventArgs e)
{
WebClient webClient = new WebClient();
webClient.DownloadFileAsync(new Uri("API-URL"), contentZip);
webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(InstallContent);
}
This is what I want the button to look like (skipping the other stuff like background color, etc.)
<Button Name="Downloadbutton"
Content="Download"
Click="DownloadContent"
</Button>
Am I missing something as why I get this Error and how could I fix it?
EDIT
The Installcontent function looks like this:
private void InstallContent(object sender, AsyncCompletedEventArgs e)
{
ZipFile.ExtractToDirectory(contentZip, contentPath, true);
File.Delete(contentZip);
writeJsonData(DLCbuttonTag);
Downloadbutton.Visibility = Visibility.Collapsed;
Uninstallbutton.Visibility = Visibility.Visible;
}
The function that creates the button:
public static void getContentList()
{
string jsonString = File.ReadAllText(#"C:\Users\stage\OneDrive\Documenten\ContentInfo.json")
ContentList contentList = JsonConvert.DeserializeObject<ContentList>(jsonString);;
for (int i = 0; i < contentList.Content.Count; i++)
{
StackPanel dynamicStackPanel = new StackPanel();
dynamicStackPanel.Orientation = Orientation.Horizontal;
dynamicStackPanel.Background = Brushes.Transparent;
Button downloadButton = new Button();
downloadButton.Click += new RoutedEventHandler(DownloadContent);
dynamicStackPanel.Children.Add(downloadButton);
}
}
The problem is that getContentList is declared as static. You try to reference the method DownloadContent which is an instance method. Remove the static from getContentList to fix the error.
The reason for the error is that static methods cannot access instance members. Static members do not run in the context of a particular instance, but in the context of the type itself. While in the case of WPF there most likely is no other instance of your page, in theory there could be many. The static method would not know which instance to pick, hence the error.
See this link for details on static members.
As a sidenote: in C#, methods are usually started with a capital letter (Pascal casing) regardless of their access modifier. So getContentList would be GetContentList.

No overload for method 'OnTimerEvent' takes '1' arguments

I want to show a text in a label for a particular time in my form and this is the code i tried so far :
private void ShowTextForParticularTime(String caption)
{
Timer t = new Timer { Interval = 2000, Enabled = true };
t.Tick += new EventHandler(OnTimerEvent(caption));
}
private void OnTimerEvent(object sender, EventArgs e,String caption)
{
barStaticItem3.Caption = caption;
}
My question is how can I set the "caption" parameter into OnTimerEvent method because that code I wrote doesn't work it gives me this error :
No overload for method 'OnTimerEvent' takes '1' arguments
Use this instead:
t.Tick += (sender, args) => OnTimerEvent(sender, args, caption);
Reason is you need to assign some event handler to the event. But when you state new EventHandler(OnTimerEvent(caption)); you are actually trying to invoke it. The invocation call at compile time of course fails because the method requires 3 parameters (sender, e, caption).
If instead you create an anonymous delegate via lamdas, you can take advantage of their syntax and closure to wire the event while passing in your third caption parameter.

DoWork of BackgroundWorker is called twice when RunWorkerAsync is called once?

I have create a backgroundworker in an class it works, but if i call and wait until the end run, call it for the second time it will do the same process twice
i thinks there is somthing wrong with bw.DoWork +=
private void button1_Click(object sender, EventArgs e)
{
nptest.test.start("null", "null");
}
namespace nptest
{
class test
{
public static void start(string str, string strb)
{
if (bw.IsBusy != true)
{
bw.WorkerSupportsCancellation = true;
bw.DoWork += (obj, e) => bw_DoWork(str, strb);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
}
}
private static BackgroundWorker bw = new BackgroundWorker();
private static void bw_DoWork(string str, string strb)
{
System.Windows.Forms.MessageBox.Show("initializing BackgroundWorker");
}
private static void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
Console.WriteLine("Canceled");
}
else if (!(e.Error == null))
{
Console.WriteLine("Error: " + e.Error.Message);
}
bw.Dispose();
}
}
}
problem solved
class test
{
private static List<object> arguments = new List<object>();
// initializing with program startup
public static void bwinitializing()
{
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
public static void start(string str, string strb)
{
if (bw.IsBusy != true)
{
arguments.Clear();
arguments.Add(str);
arguments.Add(strb);
bw.RunWorkerAsync(arguments);
}
}
private static BackgroundWorker bw = new BackgroundWorker();
private static void bw_DoWork(object sender, DoWorkEventArgs e)
{
List<object> genericlist = e.Argument as List<object>;
System.Windows.Forms.MessageBox.Show("BackgroundWorker " + genericlist[0]);
}
I would suspect that multiple DoWork events are being inadvertently added.
That is, every time the start method is called it registers a new DoWork event handler. This adds and does not replace the existing handler DoWork handler. So then there will be multiple DoWork handlers called subsequent times .. 1, 2, 3, etc.
// creates a NEW delegate and adds a NEW handler
bw.DoWork += (obj, e) => bw_DoWork(str, strb);
I would recommend not using a closure here, but rather just use a Method Group (with implicit conversion to a delegate) and then pass the data to the RunWorkerAsync call (there is a form that takes an argument for data).
The RunWorkerCompleted += line doesn't have this issue because it is passed a delegate from a Method Group (which is guaranteed to always evaluate to the same delegate object1). Thus the repeated += calls for that line will replace the handler.
Example:
class MyData {
public string StrA { get; set; }
}
// These only need to be setup once (and should be for clarity).
// However it will be "ok" now if they are called multiple times
// as, since the delegates are the same, the += will
// act as a replacement (as it replaces the previous delegate with itself).
bw.WorkerSupportsCancellation = true;
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
// Pass data via argument
bw.RunWorkerAsync(new MyData {
StrA = str,
});
void bw_DoWork (object sender, DoWorkEventArgs e) {
var data = (MyData)e.Argument;
var str = data.StrA;
// stuff
}
1 I am not sure if it is guaranteed to be reference-equals equality, but using this approach allows for stable invoking of += and -= from the delegate from the Method Group even if obtained by new DelegateType(MethodGroup).
Wrt. my comment in the main post: if UI elements are accessed from a thread on which they were not created then there will fun "Cross-thread operation exceptions". I believe this usage of a Message Box is "okay" (when not created with an owner from another thread), but the practice of accessing the UI in a BackgroundWorker's DoWork is generally dubious.
Also, do not call bw.Dispose() here; dispose it with the owning container or context. It appears to be nice and benign in this case, but only do it when that BGW instance will never be used again. Calling it from an event handler is also dubious as the BGW is still "active".
I have encounter same problem as above commenter "Power-Mosfet"
and in the end, added a new BackgroundWorker() then assigned to the global bw value will fix my problem.
code is, change from:
private BackgroundWorker gBgwDownload;
private void yourFunction_bw(xxx)
{
// Create a background thread
gBgwDownload.DoWork += bgwDownload_DoWork;
gBgwDownload.RunWorkerCompleted += bgwDownload_RunWorkerCompleted;
//omited some code
gBgwDownload.RunWorkerAsync(paraObj);
}
to:
private BackgroundWorker gBgwDownload;
private void yourFunction_bw(xxx)
{
// Create a background thread
gBgwDownload = new BackgroundWorker(); /* added this line will fix problem */
gBgwDownload.DoWork += bgwDownload_DoWork;
gBgwDownload.RunWorkerCompleted += bgwDownload_RunWorkerCompleted;
//omited some code
gBgwDownload.RunWorkerAsync(paraObj);
}
There is also another reason. look for DoWorkEventHandler in its generated code InitializeComponent() If you have generated it through compnent UI properties and also registering it yourself.
Because if you register it again it will not override the previous one but will add another event and will call twice.
In my case, BackgroundWorker was running twice because in the constructor class of my form I declared the DoWork, ProgressChanged and RunWorkerCompleted event handlers, but it was already declared by Visual Studio 2013 in Designer part of this form class.
So, I just deleted my declarations and it worked fine.
thank you....this code is working fine... creating new intance for backroundworker is good idea....
Now we can call this function in for/while loop and can run multiple backgroundworker process.
I coded like this
when button click is done.. without distrubting the main thread flow... multiple process will be running back side....
i just used messagebox to pop up..but we can do timetaking process to run in "bgwDownload_DoWork" function... and multiple process will be created... and her we need not check the BackgroundWorker is busy or not...
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 3; i++)
yourFunction_bw(i);
}
private BackgroundWorker gBgwDownload;
private void yourFunction_bw(int i)
{
// Create a background thread
gBgwDownload = new BackgroundWorker(); // added this line will fix problem
gBgwDownload.DoWork += bgwDownload_DoWork;
gBgwDownload.RunWorkerAsync(i);
}
private void bgwDownload_DoWork(object sender, DoWorkEventArgs e)
{
int stre = (int)e.Argument;
MessageBox.Show(stre.ToString ()); // time taken process can be added here
}
I ran into this problem today, I put a background worker on a popup form that was doing a long running task when I noticed that every time I showed the form the background worker RunWorkerCompleted event was being called multiple times.
My problem was that I was not disposing of the form after closing it, which meant every time I showed the form it added another handler to the even each time.
Disposing of the form when finished with it solved my problem. Just wanted to mention it here as I came across this page when I went looking for a solution for my situation.
I removed the control from the designer and instantiate a new WorkerProcess in Code:
example:
var bwProcess = new BackgroundWorker();
bwProcess.DoWork += new DoWorkEventHandler(bwProcess_DoWork);
bwProcess.RunWorkerCompleted += bwProcess_RunWorkerCompleted;

passing values between forms (winforms)

Wierd behaviour when passing values to and from second form.
ParameterForm pf = new ParameterForm(testString);
works
ParameterForm pf = new ParameterForm();
pf.testString="test";
doesn't (testString defined as public string)
maybe i'm missing something? Anyway I'd like to make 2nd variant work properly, as for now - it returns null object reference error.
Thanks for help.
Posting more code here:
calling
Button ParametersButton = new Button();
ParametersButton.Click += delegate
{
ParameterForm pf = new ParameterForm(doc.GetElementById(ParametersButton.Tag.ToString()));
pf.ShowDialog(this);
pf.test = "test";
pf.Submit += new ParameterForm.ParameterSubmitResult(pf_Submit);
};
definition and use
public partial class ParameterForm : Form
{
public string test;
public XmlElement node;
public delegate void ParameterSubmitResult(object sender, XmlElement e);
public event ParameterSubmitResult Submit;
public void SubmitButton_Click(object sender, EventArgs e)
{
Submit(this,this.node);
Debug.WriteLine(test);
}
}
result:
Submit - null object reference
test - null object reference
pf.ShowDialog(this); is a blocking call, so pf.Submit += new ParameterForm.ParameterSubmitResult(pf_Submit); is never reached: switch the order.
Submit(this,this.node); throws a null object reference because no event is assigned to it (see above). Generally, you should always check first: if (Submit != null) Submit(this,this.node);
You should change ``pf.ShowDialog(this);topf.Show(this);` so that your main form isn't disabled while your dialog box is open, if that's what you want, or use the model below (typical for dialog boxes.)
I'm not sure what pf_Submit is supposed to do, so this might not be the best way to go about it in your application, but it's how general "Proceed? Yes/No" questions work.
Button ParametersButton = new Button();
ParametersButton.Click += delegate
{
ParameterForm pf = new ParameterForm(testString);
pf.ShowDialog(this); // Blocks until user submits
// Do whatever pf_Submit did here.
};
public partial class ParameterForm : Form
{
public string test; // Generally, encapsulate these
public XmlElement node; // in properties
public void SubmitButton_Click(object sender, EventArgs e)
{
Debug.WriteLine(test);
this.Close(); // Returns from ShowDialog()
}
}
When you want to use your second variant, you have to use a getString()-Method, where you can put the e.g. "testString". The way you wrote it, "testString" should be a method (and got brackets).
EDIT (a bit more precise):
You could write:
pf.getString(testString);
, if "pf" is an instance of your own class, otherwise you had to look up, whether you can retrieve a String in this class.
the thing was in line order :)
pf.Submit += new ParameterForm.ParameterSubmitResult(pf_Submit);
and
pf.Test = "test";
should have been set before
pf.ShowDialog(this);
my mistake thingking that parameter can be passed after 2nd form was displayed
thnx for answers

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