I have this code;
Button button = new Button();
MessageBox ms = new MessageBox(button);
Action<bool> action = ms.Show();
action += (b) =>
{
Console.WriteLine(b.ToString()); //this isnt working
Console.WriteLine("??");
};
Console.Read();
button.OnClick();
Console.ReadKey();
MessageBox class :
class MessageBox
{
Button _button;
public MessageBox(Button button) { _button = button; }//initialize button
public Action<bool> Show()
{
Action<bool> action = new Action<bool>(CallForTest);
_button.OnClick+=()=>{ action?.Invoke(true); };
return action;
}
//...working.*//
public void CallForTest(bool statu){}
}
I want to return an action and when button is clicked,call the action.But this isnt working? What is the problem? Action is a delegate so delegate is a reference type?(compiler generated class) What is wrong in this picture?
I think when "Show()" is ends,"action" is collected from gargabe collector.But this is working with other reference types? for example;
public Test Show()
{
Test test = new Test("??");
button.OnClick += () =>
{
test.JustForTest(); //working (cw("?????" + ctorvalue);
};
return test;
}
Delegates are immutable. When you are combining two delegates using +=, you are actually creating a new delegate. So when you have done act += ... in the above code, you have actually created a new delegate, it is different from what you have already created in Show() method.
I believe this is happening because when you use += to a delegate it does not append to the internal list. This is why you don't see b.string() being printed
Without changing your design you won't be able to append the action to the original delegate when the button is clicked.
What you are actually writing is somthing like:
var act2 = new Action<bool>((b) =>
{
Console.WriteLine(b.ToString()); //this isnt working
Console.WriteLine("??");
});
var act = act + act2;
as you can see act is getting a new reference to the combined expression of act + act2 rather than act itself concatenating act2 internally.
if you do act(false) you will see the extra results, but not if you invoke the button click.
What you should be using is event on the delegate within the Button, which is the way UI controls are written
class Button
{
public event EventHandler<BoolEventArgs> Click;
}
best to read up on using events when you want to have multicast delegates in this way. MSDN site
Related
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.
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
This code should take a piece of information from a webpage. My problem is that it doesn't show correnctly and don't know why.
I want to make it somehow to wait for document completion without creating a function outside that one.
The actual code that I want to fix from a larger file:
public static string GetNews()
{
WebBrowser page = new WebBrowser();
string data = null;
page.Navigate(launcherScriptAddress);
page.DocumentCompleted += delegate {
data = page.Document.GetElementById("news").InnerText;
// can't return `data` from here
};
return data; // returns null because it doesn't wait for document to be completed
}
This won't work the way you try to do it. Your function returns way before the page.DocumentCompleted-delegate is being executed.
So the only thing you can do is pass a callback to your GetNews-function that gets executed within your delegate.
The old problem with sync <-> async.
Besides, you should swap the statements .Navigate and .DocumentCompleted +=
to make sure "page" has the callback set before it even starts to load anything.
[edit]
To do that, you need to create a delegate and change your function:
public delegate void NewsCallback( string dataReceived );
public static void GetNews( NewsCallback callback )
{
WebBrowser page = new WebBrowser();
string data = null;
page.Navigate(launcherScriptAddress);
page.DocumentCompleted += delegate {
data = page.Document.GetElementById("news").InnerText;
callback( data );
};
}
After that, you may want to call it this way:
void CallMyNews(){
GetNews( (dataReceived) => {
DoSomeStuffWith(dataReceived);
} );
}
I have the following:
public String AttachService(string whereClauseParam)
{
//Get Client object here
Client c = new Client();
string cookieFromRequest = WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Cookie];
tokenInfo.TryGetValue(cookieFromRequest, out c);
string[] arr = new string[] { };
c.AttachedServiceStatus += OnAttachedServiceStatus;
string whereClause = whereClauseParam.ToString();
//c.AttachService("binding.interface='query_em'", 8799989);
return string.Format("attached");
}
//Handler code below:
public string OnAttachedServiceStatus(Client sender, ClientServiceAttachedStatus status)
{
if (status.AttachStatus == AttachedStatus.Connected && status.ServiceAttachStatus == ServiceAttachStatus.Attached)
{
//update the Client object in Dictionary
Client c = new Client();
var ou = tokenInfo.First(x => x.Value == sender);
tokenInfo.TryGetValue(ou.Key.ToString(), out c);
tokenInfo.TryRemove(ou.Key.ToString(), out c);
tokenInfo.TryAdd(ou.Key.ToString(), sender);
string[] statusInfoT = new string[200];
statusInfoT[0] = status.ServiceId.ToString();
statusInfoT[1] = status.AttachStatus.ToString();
statusInfoT[2] = status.ServiceAttachStatus.ToString();
statusInfoT[3] = status.VirtualServiceId.ToString();
statusInfoT[4] = status.AttachToken.ToString();
statusInfo.TryAdd(ou.Key.ToString(), statusInfoT);
//update the UI with a Dispatch - TO BE DONE
}
return "Connected";
}
The above AttachService method has a handler "OnAttachedServiceStatus" attached to an event "AttachedServiceStatus".
As long as the OnAttachedServiceStatus return void, it all works well. However, i now need to have the Handler OnAttachedServiceStatus to return a string but i'm not able to attach the handler correctly.
I'm thinking of using the Func delegate but not sure how to use it.
Please Help!
First of all, signature of event handler is defined by event's delegate type. If that delegate returns void, then you cannot attach any other methods. Both parameters of method and return value should match signature of even's delegate. I believe AttachedServiceStatus uses delegate which returns void. Something like that:
public delegate void Action<T1, T2>(T1 arg1, T2 arg2)
And event is
public event Action<Client, ClientServiceAttachedStatus> AttachedServiceStatus
But what if you'll use delegate which returns value? E.g.
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2)
You can declare event as
public event Func<Client, ClientServiceAttachedStatus> AttachedServiceStatus
But it makes no sense. Because event is a delegate. When you attach handler, you are actually combining delegates, creating something like list of delegates (invocation list). This list contains all attached handlers. When you raise event, all handlers in invocation list are invoked one by one, and only result of last invoked handler is returned. Order of invokation is not determined. So you even don't know which handler returned value.
(*) Though it is still possible to get all results if you will invoke each handler manually instead of raising event. See Servy comment below
UPDATE
I want the handler "OnAttachedServiceStatus" to return a string back
to Caller "AttachService" but i cannot get the following correct
When you attach handler to event, handler is not executed. It just added to invocation list of event. Event handler will be executed when Client will raise event. So AttachService is not a caller here. Client is a caller. And you cannot return string back to AttachService. After attaching handler to event, code will exit AttachService method. Some time later event will be raised, and handler will be executed, but it will not be related to AttachService method.
I'm not sure you're understanding how events work.
Client c = new Client();
// ...
c.AttachedServiceStatus += OnAttachedServiceStatus;
OnAttachedServiceStatus is not being called here. Instead, this is telling the Client object to invoke the OnAttachedServiceStatus method whenever it raises the AttachedServiceStatus event, which may happen at any point in the future. It's like you telling a racer "When I say 'go', run as fast as you can to the finish line then tell me how many steps it took you to get there". The racer doesn't immediately start running at that point, nor do they tell you how many steps it took; they get in the ready position and wait. When you say "go", that's when they execute your instructions and start running. By the time you get their response, it's well after you gave him the instruction to wait.
From the looks of things, you're attempting to establish a remote connection and are wanting confirmation back from the server that a connection has indeed been established. If using events to convey that information, you'll want to use the EventArgs to carry it. You should be able to achieve that with something like this:
Client side:
public String ConnectToServer(string whereClauseParam)
{
//Create Server object here
Server s = new Server();
s.AttachedServiceStatus += OnAttachedServiceStatus;
s.AttachService(this, whereClauseParam, 8799989);
}
public void OnAttachedServiceStatus (object sender, ClientServiceAttachedEventArgs e)
{
if (e.AttachStatus == AttachedStatus.Connected && e.ServiceAttachStatus == ServiceAttachStatus.Attached)
{
// Update the UI with the message from the server.
MessageBox.Show(e.Message);
// If you need to do something else with the server in response, you can do this:
((Server)sender).Foo("bar");
}
}
And on the server side, define the custom EventArgs class for your event:
// By making this inherit from EventArgs, we can use the built-in EventHandler<T> delegate for the event itself.
public class ClientServiceAttachedEventArgs : EventArgs
{
public AttachedStatus AttachStatus { get; set; }
public ServiceAttachStatus ServiceAttachStatus { get; set; }
public string Message { get; set; }
// You can put in as many properties as you want to carry the information back from the server.
}
And put this in your Server class:
public event EventHandler<ClientServiceAttachedEventArgs> AttachedServiceStatus;
public String AttachService(Client client, string whereClauseParam, int code)
{
// Do what you need to do to register the client.
//...
// Assuming everything went as planned, fire the event.
// First, construct the EventArgs with information about the results of the connection.
ClientServiceAttachedEventArgs e = new ClientServiceAttachedEventArgs();
e.AttachStatus = AttachedStatus.Connected;
e.ServiceAttachStatus = ServiceAttachStatus.Attached;
e.Message = "Attached";
// This is where your OnAttachedServiceStatus method in the client finally gets called. If the event handler were returning a string, this is where it would be returned to and I can't imagine this does you any good.
AttachedServiceStatus(this, e);
}
This is a fairly basic implementation and your situation is probably more complex but it should point you in the right direction. The important thing to note is that the string that you wanted returned back to the client is coming through the event as part of the ClientServiceAttachedEventArgs, along with your status enums. This is the preferred way of sending information through events.
I have a strange problem with devexpress AlertControl. I create an alertu using this code
AlertInfo alertInfo = new AlertInfo(caption, text);
AlertControl control = new AlertControl();
control.FormLocation = AlertFormLocation.BottomRight;
control.Show(null,alertInfo);
this code is placed in backgroundWorker_DoWork function and it is supposed to display alerts from time to time. The problem is that alerts are not shown. I can see that show method is invoked however alerts are not shown.
Acording to documentation is I pass null as a parametr of Show function , notification should be shown on main monitor.
What can I do to make it work ?
Considering you're using a worker, I guess it's a thread problem. Try wrapping your code inside an Action object:
Action action = () =>
{
AlertControl control = new AlertControl();
control.FormLocation = AlertFormLocation.BottomRight;
control.Show(this, alertInfo); // "this" being a Form
};
this.Invoke(action);
I use a similar code inside a form with good results and once did a similar code using an AlertControl too.
Your AlertControl need a Parent Control.
AlertControl control = new AlertControl();
control.FormLocation = AlertFormLocation.BottomRight;
control.Show(MyForm,alertInfo); //replace null with a Form/Control instance
You call the Show method with a null paramater - where you should have use an instance of a Form/Control
Don't know anything about the devexpress controls, but maybe you have to show the alert from the main thread via invoke methode?
using DevExpress.XtraBars.Alerter;
// Create a regular custom button.
AlertButton btn1 = new AlertButton(Image.FromFile(#"c:\folder-16x16.png"));
btn1.Hint = "Open file";
btn1.Name = "buttonOpen";
// Create a check custom button.
AlertButton btn2 = new AlertButton(Image.FromFile(#"c:\clock-16x16.png"));
btn2.Style = AlertButtonStyle.CheckButton;
btn2.Down = true;
btn2.Hint = "Alert On";
btn2.Name = "buttonAlert";
// Add buttons to the AlertControl and subscribe to the events to process button clicks
alertControl1.Buttons.Add(btn1);
alertControl1.Buttons.Add(btn2);
alertControl1.ButtonClick += new AlertButtonClickEventHandler(alertControl1_ButtonClick);
alertControl1.ButtonDownChanged +=
new AlertButtonDownChangedEventHandler(alertControl1_ButtonDownChanged);
// Show a sample alert window.
AlertInfo info = new AlertInfo("New Window", "Text");
alertControl1.Show(this, info);
void alertControl1_ButtonDownChanged(object sender,
AlertButtonDownChangedEventArgs e) {
if (e.ButtonName == "buttonOpen") {
//...
}
}
void alertControl1_ButtonClick(object sender, AlertButtonClickEventArgs e) {
if (e.ButtonName == "buttonAlert") {
//...
}
}
ref:https://documentation.devexpress.com/#WindowsForms/clsDevExpressXtraBarsAlerterAlertControltopic