I am trying to capture events from an existing IE window. In the code sample below, I am attempting to capture the mouseClick event within the browser document when a user clicks on an element, and then eventually pull back some attributes about the element being clicked.
public partial class frmBrowserElementBuilder : Form
{
InternetExplorer ie;
public frmBrowserElementBuilder()
{
InitializeComponent();
}
private void frmBrowserElementBuilder_Load(object sender, EventArgs e)
{
//create IE
ie = new InternetExplorer();
ie.Visible = true;
//handle document completed
ie.DocumentComplete += new
DWebBrowserEvents2_DocumentCompleteEventHandler(DocumentComplete);
}
public void DocumentComplete(object pDisp, ref object URL)
{
//document was loaded
//MessageBox.Show("DocumentComplete: " + URL);
//create event handler and hook onclick from IE
DHTMLEventHandler onClickHandler = new DHTMLEventHandler(ie.Document);
onClickHandler.assignedEvent += new DHTMLEvent(this.ie_onClick);
ie.Document.onclick = onClickHandler;
}
private void ie_onClick(mshtml.IHTMLEventObj e)
{
//something was clicked
MessageBox.Show(string.Format("Event Hooked {0}, Qualifier {1}", e.type, e.qualifier));
}
public delegate void DHTMLEvent(IHTMLEventObj e);
[ComVisible(true)]
public class DHTMLEventHandler
{
public DHTMLEvent assignedEvent;
private mshtml.HTMLDocument document;
public DHTMLEventHandler(mshtml.HTMLDocument doc)
{
//assign to instance of IE document
this.document = doc;
}
[DispId(0)]
public void Call()
{
//call the event
assignedEvent(this.document.parentWindow.#event); //{System.InvalidCastException: "Specified cast is not valid."}
}
}
}
The code compiles and the void Call() triggers as expected, however, the value of this.document.parentwindow is null and is throws System.InvalidCastException: Specified cast is not valid when stepping into the assignedEvent method.
When I inspect this.document, the value of parentWindow states
The function evaluation requires all threads to run.
after forcing evaluation it states:
'((mshtml.HTMLDocumentClass)this.document).parentWindow' threw an
exception of type 'System.InvalidCastException'.
Any ideas?
This is a threading issue. The Call() call happens on an MTA thread, and you can't access MSHTML from an MTA thread. There are many ways to change this, however, the most simple is to do this:
public void DocumentComplete(object pDisp, ref object URL)
{
var events = (HTMLDocumentEvents2_Event)ie.Document;
events.onclick += (evt) =>
{
MessageBox.Show(string.Format("Event Hooked {0}, Qualifier {1}", evt.type, evt.qualifier));
return false;
};
}
Related
I'm working on writing a class which is derived from the System.ComponentModel.BackgroundWorker class. The reason I am doing so in my project is that I need a lot of information to be returned in different types of status update events, depending on which event is raised. When attempting to update any of the controls the main form from any of my update events, I am getting the following error:
System.InvalidOperationException: 'Cross-thread operation not valid:
Control '' accessed from a thread other than the thread it was created
on.'
The first control that I am attempting to update is a ToolStripStatusLabel, which does not have an .Invoke() method. I have created minimally verifiable example below. To recreate the error, simply create a new Windows Forms App (.NET Framework) project targeted to .NET 4.8 and copy paste the following code into the Form1.cs file:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private StatusStrip statusStrip1;
private ToolStripStatusLabel toolStripStatusLabel1;
private ToolStripProgressBar toolStripProgressBar1;
private Button button1;
private MyBGW myBGW;
public Form1()
{
InitializeComponent();
this.statusStrip1 = new StatusStrip();
this.toolStripStatusLabel1 = new ToolStripStatusLabel() { Text = "Starting Text" };
this.toolStripProgressBar1 = new ToolStripProgressBar();
this.button1 = new Button();
this.myBGW = new MyBGW();
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {this.toolStripStatusLabel1, this.toolStripProgressBar1});
this.Controls.Add(this.statusStrip1);
this.Controls.Add(this.button1);
this.button1.Click += Button1_Click;
this.myBGW.OnMyBGW_StatusChanged += MyBGW_OnMyBGW_StatusChanged;
}
private void Button1_Click(object sender, EventArgs e) { myBGW.RunWorkerAsync(); }
private void MyBGW_OnMyBGW_StatusChanged(object sender, MyBGW.MyBGW_StatusChanged_EventArgs e)
{
// The following two lines will throw the cross-threading exception
this.toolStripStatusLabel1.Text = e.StatusText;
if (e.PBarStyle != MyBGW.pBarStyles.NoChange) { this.toolStripProgressBar1.Style = (ProgressBarStyle)e.PBarStyle; }
}
}
public class MyBGW : BackgroundWorker
{
public enum pBarStyles { Block = 0, Continuous = 1, Marquee = 2, NoChange = -1 }
public delegate void MyBGW_StatusChanged_EventHandler(object sender, MyBGW_StatusChanged_EventArgs e);
public event MyBGW_StatusChanged_EventHandler OnMyBGW_StatusChanged;
public class MyBGW_StatusChanged_EventArgs : EventArgs
{
public string StatusText;
public pBarStyles PBarStyle;
public MyBGW_StatusChanged_EventArgs(string statusText, pBarStyles pBarStyle)
{
this.StatusText = statusText; this.PBarStyle = pBarStyle;
}
}
public new void RunWorkerAsync() { base.RunWorkerAsync(); }
private void myBGW_DoWork(object sender, DoWorkEventArgs e)
{
OnMyBGW_StatusChanged(this, new MyBGW_StatusChanged_EventArgs(DateTime.Now.ToString(), pBarStyles.Marquee));
System.Threading.Thread.Sleep(10000);
OnMyBGW_StatusChanged(this, new MyBGW_StatusChanged_EventArgs("Done", pBarStyles.Continuous));
}
public MyBGW() { base.DoWork += new DoWorkEventHandler(this.myBGW_DoWork); }
}
}
My best guess is that I am raising or consuming the event incorrectly which is causing the code to still be run on the worker thread instead of the main/UI thread, but I'm coming up short in my research on what I'm missing.
EDIT: this question is not related to Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on as it is not directly relying on a BackgroundWorker but is rather attempting to add additional events to a derived class, of which the addition of those events are causing the Cross-Thread exception. Also, the answer does not apply as the control attempting to be updated does not have the .Invoke method as the solution to that question stated.
The problem for this question is in relation to how the event was being raised, which was incorrectly, causing the consumption of that event to be on the wrong thread and raising the cross-thread exception.
The BackgroundWorker.DoWork event handler is supposed to do background work, and it's not intended for interacting with the UI. This handler is invoked on a ThreadPool thread, and interacting with UI components from any thread other than the UI thread is not allowed. The BackgroundWorker class offers two events that are raised on the UI thread¹, the ProgressChanged and the RunWorkerCompleted. You could take advantage of this, by invoking your StatusChanged event on the ProgressChanged event handler (or overriding the OnProgressChanged method), and passing your StatusChangedEventArgs as an argument of the ReportProgress method:
public class MyBGW : BackgroundWorker
{
public enum BarStyles { Block = 0, Continuous = 1, Marquee = 2, NoChange = -1 }
public delegate void StatusChangedEventHandler(object sender,
StatusChangedEventArgs e);
public event StatusChangedEventHandler StatusChanged;
public MyBGW() { this.WorkerReportsProgress = true; }
public class StatusChangedEventArgs : EventArgs
{
public string StatusText;
public BarStyles PBarStyle;
public StatusChangedEventArgs(string statusText, BarStyles pBarStyle)
{
this.StatusText = statusText; this.PBarStyle = pBarStyle;
}
}
protected override void OnDoWork(DoWorkEventArgs e)
{
this.ReportProgress(-1,
new StatusChangedEventArgs(DateTime.Now.ToString(), BarStyles.Marquee));
base.OnDoWork(e);
this.ReportProgress(-1,
new StatusChangedEventArgs("Done", BarStyles.Continuous));
}
protected override void OnProgressChanged(ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == -1 && e.UserState is StatusChangedEventArgs args)
StatusChanged?.Invoke(this, args);
else
base.OnProgressChanged(e);
}
}
¹ To be precise, the ProgressChanged and RunWorkerCompleted events are raised on the SynchronizationContext.Current which is captured when the BackgroundWorker.RunWorkerAsync is invoked.
Because toolStripStatusLabel1 And toolStripProgressBar1 runs inside a thread other than the main thread, it needs to be Invoke. And since ToolStripStatusLabel And ToolStripProgressBar itself does not have an Invoke method, we use its parent Invoke method.
change MyBGW_OnMyBGW_StatusChanged to :
private void MyBGW_OnMyBGW_StatusChanged(object sender, MyBGW.MyBGW_StatusChanged_EventArgs e)
{
InvokeIfRequired(this, ()=>
{
this.toolStripStatusLabel1.Text = e.StatusText;
});
if (e.PBarStyle != MyBGW.pBarStyles.NoChange)
{
InvokeIfRequired(this, () =>
{
this.toolStripProgressBar1.Style = (ProgressBarStyle)e.PBarStyle;
});
}
}
add InvokeIfRequired method
public void InvokeIfRequired(Control control, MethodInvoker action)
{
if (control.InvokeRequired)
control.Invoke(action);
else
action();
}
As mjwills has stated in the comments of the question, I was not raising the event properly, which was causing the event to be consumed on the same worker thread. After looking at the link for the .NET source code of the BackgroundWorker class, I can see that there is a bit of code, AsyncOperation.Post() that has the method protected virtual void OnStatusChangedin the code below raised in the main thread rather than the worker thread.
public class MyBGW : BackgroundWorker
{
public enum pBarStyles { Block = 0, Continuous = 1, Marquee = 2, NoChange = -1 }
private static readonly object statusChangedKey = new object();
private AsyncOperation asyncOperation = null;
public MyBGW() { base.DoWork += new DoWorkEventHandler(this.myBGW_DoWork); }
public delegate void StatusChanged_EventHandler(object sender, StatusChanged_EventArgs e);
public event StatusChanged_EventHandler StatusChanged
{
add { this.Events.AddHandler(statusChangedKey, value); }
remove { this.Events.RemoveHandler(statusChangedKey, value); }
}
protected virtual void OnStatusChanged(StatusChanged_EventArgs e) { ((StatusChanged_EventHandler)Events[statusChangedKey])?.Invoke(this, e); }
private void StatusReporter(object arg) { OnStatusChanged((StatusChanged_EventArgs)arg); }
public void UpdateStatus(StatusChanged_EventArgs e) { asyncOperation.Post(new System.Threading.SendOrPostCallback(StatusReporter), e); }
public class StatusChanged_EventArgs : EventArgs
{
public string StatusText;
public pBarStyles PBarStyle;
public StatusChanged_EventArgs(string statusText, pBarStyles pBarStyle)
{
this.StatusText = statusText; this.PBarStyle = pBarStyle;
}
}
public new void RunWorkerAsync() { asyncOperation = AsyncOperationManager.CreateOperation(null); base.RunWorkerAsync(); }
private void myBGW_DoWork(object sender, DoWorkEventArgs e)
{
UpdateStatus(new StatusChanged_EventArgs(DateTime.Now.ToString(), pBarStyles.Marquee));
System.Threading.Thread.Sleep(3000);
UpdateStatus(new StatusChanged_EventArgs("Done", pBarStyles.Continuous));
}
}
I don't fully understand the how and why, but it works. Hopefully someone can comment below with a better explanation.
I'm struggling to pass data between a thread started in a separate class from my main form. I believe (I could be wrong) that I should use an event. The problem I have is my subscribers are always null as I call the BluetoothScan class and start the thread before the event is subscribed to:
BluetoothScan bluetoothScan = new BluetoothScan(this);
bluetoothScan.BluetoothDeviceDiscovered += OnBluetoothDeviceDiscovered;
How do I subscribe to the event before starting the thread?
I have my Main Form:
using System;
using System.Windows.Forms;
//https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.invoke?view=net-5.0#System_Windows_Forms_Control_Invoke_System_Delegate_System_Object___
namespace YieldMonitor
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
}
private void BtnConnectBT_Click(object sender, EventArgs e)
{
//Start looking for the yield monitor device.
BluetoothScan bluetoothScan = new BluetoothScan(this);
bluetoothScan.BluetoothDeviceDiscovered += OnBluetoothDeviceDiscovered;
}
static void OnBluetoothDeviceDiscovered(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Message recieved from event");
}
}
}
My class that looks for bluetooth devices and if the right one is found should fire the event:
using InTheHand.Net.Sockets;
using System;
using System.Linq;
namespace YieldMonitor
{
class BluetoothScan
{
public event EventHandler BluetoothDeviceDiscovered;
public BluetoothScan(MainForm mainForm)
{
System.Diagnostics.Debug.WriteLine("Starting BluetoothScan Class");
Run();
}
public void Run()
{
System.Diagnostics.Debug.WriteLine("Running BluetoothScan Class");
string myDeviceName;
ulong myDeviceAddress;
BluetoothClient btClient = new BluetoothClient();
BluetoothDeviceInfo[] btDevices = btClient.DiscoverDevices().ToArray();
foreach (BluetoothDeviceInfo d in btDevices)
{
System.Diagnostics.Debug.WriteLine(d.DeviceName);
System.Diagnostics.Debug.WriteLine(d.DeviceAddress);
//have we found the device we are looking for?
if (d.DeviceName == "DSD TECH HC-05")
{
myDeviceName = d.DeviceName;
myDeviceAddress = d.DeviceAddress;
//Send out found adapter to the next stage
OnBluetoothScanned(EventArgs.Empty);
break;
}
}
}
protected virtual void OnBluetoothScanned(EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Running OnBlueToothScanned");
EventHandler handler = BluetoothDeviceDiscovered;
if (handler != null)// we have a subscriber to our event
{
System.Diagnostics.Debug.WriteLine("BluetoothScanned is Not empty");
handler(this, e);
}
else
{
System.Diagnostics.Debug.WriteLine("BluetoothScanned is Empty");
}
}
}
}
EDIT
I've found some nice solutions using Tasks where I need to update a label once a task is completed ie.
bool myDevicePaired = false;
var eventDevicePaired = new Progress<bool>(boDevicePaired => myDevicePaired = boDevicePaired);
await Task.Factory.StartNew(() => BluetoothPair.Run(myDeviceAddress, eventDevicePaired), TaskCreationOptions.LongRunning);
//Register the device is paired with the UI
if (myDevicePaired)
{
BtnConnectBT.Text = "Disconnect?";
}
Which is working well for Tasks that have an end that I am waiting for example waiting for a bluetooth device to connect.
But I'm beginning to pull my hair out with System.InvalidOperationException: 'Cross-thread operation not valid: Control 'tbInfo' accessed from a thread other than the thread it was created on.' error when trying to update a form text box.
Example:
in my MainForm Class:
I create what I've called an Event Reciever...
private void BluetoothSocketEventReciever(object sender, EventArgs e)
{
Debug.WriteLine("Event!!!"); //writes data to debug fine
tbInfo.AppendText("Event!!!!"); //causing error
}
I create a task to read from the device...
private void ReadDataFromDevice(UInt64 myDeviceAddress)
{
BluetoothSocket bluetoothSocket = new BluetoothSocket(myDeviceAddress);
bluetoothSocket.BluetoothDataRecieved += BluetoothSocketEventReciever;
Task.Factory.StartNew(() => bluetoothSocket.Run(), TaskCreationOptions.LongRunning);
}
In my BluetoothSocket class I have an endless while loop which will be reading data from a socket (hopefully) At the moment its just creating an empty EventArgs to trigger the Event every second:
namespace YieldMonitor
{
class BluetoothSocket
{
ulong myDeviceAddress;
public event EventHandler BluetoothDataRecieved;
public BluetoothSocket (ulong deviceAddress)
{
myDeviceAddress = deviceAddress;
}
public void Run()
{
System.Diagnostics.Debug.WriteLine("Were in BluetoothSocket ... Address: " + myDeviceAddress);
while (true)
{
Thread.Sleep(1000);
Debug.WriteLine("In BluetoothSocket - Address = " + myDeviceAddress);
OnBluetoothDataRecieved(EventArgs.Empty);
}
}
protected virtual void OnBluetoothDataRecieved(EventArgs e)
{
EventHandler handler = BluetoothDataRecieved;
if (handler != null)
{
handler(this, e);
} else
{
//No subscribers
}
}
}
}
I'm sure I'm missing something simple here but how can I pass the data from the endless loop to the text box on the main form?
EDIT
Think I've just sorted it.
private void BluetoothSocketEventReciever(object sender, EventArgs e)
{
Debug.WriteLine("Event!!!");
tbInfo.Invoke((Action)delegate
{
tbInfo.AppendText("Event!!!");
});
//tbInfo.AppendText("Event!!!!");
}
Is this the correct way to do it?
You can Pass the event handler as a parameter on the constructor
public event EventHandler BluetoothDeviceDiscovered;
public BluetoothScan(MainForm mainForm, EventHandler bluetoothDeviceDiscovered)
{
System.Diagnostics.Debug.WriteLine("Starting BluetoothScan Class");
BluetoothDeviceDiscovered += bluetoothDeviceDiscovered
Run();
}
Personally, i'm not so fun of calling method on constructor. It can be source of bugs or performance issues
Constructor
In class-based object-oriented programming, a constructor
(abbreviation: ctor) is a special type of subroutine called to create
an object. It prepares the new object for use, often accepting
arguments that the constructor uses to set required member variables.
You can pass eventhandler as parameter and call Run later
I am trying to assign an event handler that uses an event argument that extends System.EventArgs to a ToolStripMenuItem. When I enter the name of the handler it wants to create an event handler that uses System.EventArgs. This is for a list of recent files.
Here is the code
RecentEventArgs e = new RecentEventArgs();
e.FileName = item;
ToolStripMenuItem recentItem = new ToolStripMenuItem(item);
recentItem.Click += new EventHandler(RecentItem_Click);
mnuFileOpenRecentList.DropDownItems.Add(item);
private void RecentItem_Click(object sender, RecentEventArgs e)
{
MessageManager.DisplayMessageBox("File -> Open Recent ->");
OpenRecentFile(e.FileName);
}
public class RecentEventArgs : EventArgs
{
private string fileName;
public RecentEventArgs()
:base()
{
}
public string FileName
{
get { return fileName; }
set { fileName = value; }
}
}
Any help with this will be greatly appreciated.
You are heading in the right direction but you missed some key parts. Keep in mind that everything in c# is strongly typed which means that all method signatures can't change their any of their arguments at runtime. This also true for delegates (that is what you're using if you are subscribing to events).
As you already have your custom EventArgs, what is still missing is a delegate that describes the method signature your new event (because that is what you're after) will be calling.
// the public delegate with the custom eventargs
public delegate void RecentEventHandler(object sender, RecentEventArgs e);
Now you want something that acts as a ToolStripMenuItem but fires the Recent event is it gets clicked and send your custom eventargs as the payload. Let's create a subclass to handle that logic:
public class RecentFileMenuItem:ToolStripMenuItem
{
private string filename; // holds your path+file
// constructur
public RecentFileMenuItem(string filename)
:base(Path.GetFileName(filename))
{
// keep our filename
this.filename = filename;
}
// event delegate, subscribe to this
public RecentEventHandler Recent;
// click invokes all subscribers
// for the Recent Event
protected override void OnClick(EventArgs e)
{
RecentEventHandler recent = Recent;
if (recent !=null)
{
// create your RecentEventArgs here
recent(this, new RecentEventArgs { FileName = filename });
}
}
}
In the above class I override the default OnClick handler to invoke any subscribers to the Recent event where we create a new RecentEventArgs. Notice the Recent delegate of type RecentEventHandler.
With this bits working we need to bring our class into play.
private void Form3_Load(object sender, EventArgs e)
{
RecentFileMenuItem recentItem = new RecentFileMenuItem("foo");
mnuFileOpenRecentList.DropDownItems.Add(recentItem);
recentItem.Recent += new RecentEventHandler(Show);
}
// method signature that matches our RecentEventHandler delegate
public void Show(object s, RecentEventArgs e)
{
MessageBox.Show(e.FileName);
}
Here we see that we add a new instance of the RecentFileMenuItem with a filename in the constructor to the recent list. The Show method matches the signature and so we can subscribe to the Recent event with the delegate pointing to that method.
tl;dr
Implementing Class:
public Main()
{
Foo foo = new Foo();
foo.OnBarOneResponse += foo_OnBarOneResponse;
foo.OnBarTwoResponse += foo_OnBarTwoResponse;
foo.FetchBarOne();
}
void foo_OnBarOneResponse(String response)
{
// Called successfully.
this.foo.FetchBarTwo();
}
void foo_OnBarTwoResponse(String response)
{
// Never called :(
}
Foo.cs
private MyJavascriptInjector _javascriptInjector = new MyJavascriptInjector();
public delegate void OnBarOneResponseHandler(String response);
public delegate void OnBarTwoResponseHandler(String response);
public event OnBarOneResponseHandler OnBarOneResponse = delegate { };
public event OnBarTwoResponseHandler OnBarTwoResponse = delegate { };
private void _onBarOneResponse(String response)
{
// Called Successfully
OnBarOneResponse(response);
}
private void _onBarTwoResponse(String response)
{
// Never called :(
OnBarTwoResponse(response);
}
public Foo()
{
webBrowser.ObjectForScripting = _javascriptInjector;
_javascriptInjector.OnBarOneResponse += _onBarOneResponse;
_javascriptInjector.OnBarTwoResponse += _onBarTwoResponse;
webBrowser.Navigate("http://myurl", null, new Byte[0], myHeaders");
}
public void FetchBarOne()
{
webBrowser.InvokeScript("fetchBarOne");
}
public void FetchBarTwo()
{
webBrowser.InvokeScript("fetchBarTwo");
}
MyJavascriptInjector.cs
[System.Runtime.InteropServices.ComVisible(true)]
public class MyJavascriptInjector
{
public delegate void OnBarOneResponseHandler(string response);
public delegate void OnBarTwoResponseHandler(string response);
public event OnBarOneResponseHandler OnBarOneResponse;
public event OnBarTwoResponseHandler OnBarTwoResponse;
public void OnBarOneResponse(String response)
{
// Called successfully!
OnBarOneResponse(response);
}
public void OnBarTwoResponse(String response)
{
// ALSO CALLED SUCCESSFULLY BUT WHEN CALLING THIS Foo.cs event NEVER GETS FIRED.
// IT GETS LOST SOMEWHERE BETWEEN HERE and Foo.cs!
OnBarTwoResponse(response);
}
}
=================
I have an object Foo that has two methods on it, FetchBarOne and FetchBarTwo.
Each method has an event on it, OnBarOneResponse and OnBarTwoResponse.
The implementing class registers Foo's events in its constructor using the notation "+=" and defines a callback function for each: foo_OnBarOneResponse(String response) and foo_OnBarTwoResponse(String response).
PROBLEM:
The implementing class observes the following:
Calls this.foo.FetchBarOne();
foo_OnBarOneResponse(String response) is fired at a later time.
In this callback, implementing class immediately calls this.foo.FetchBarTwo();
foo_OnBarTwoResponse(String response) never fires.
MORE INFORMATION:
Foo has wrapped WebBrowser and is calling InvokeScript to execute javascript in the loaded webpage. This webpage has many javascript functions on it, including FetchBarOne and FetchBarTwo on it. When debugging, FetchBarTwo is called and it successfully responds with data. However, after WebBrowser returns the data successfully, when Foo calls its internal OnBarTwoResponseHandler event delegate that was registered by the implementing class, it gets "lost" somewhere in between - even though it is not null at it clearly has a reference to it.
FAILED ATTEMPTS OF FIXING ISSUE
Implementing class tried using Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadStart(() => this.foo.FetchBarTwo())); to try and call it from the UI thread. No success.
If anyone has any thoughts on this matter, I would be most grateful. Thanks!
The this.foo field is NOT the same instance as the foo variable.
From your example:
public Main()
{
Foo foo = new Foo();
foo.OnBarOneResponse += foo_OnBarOneResponse;
foo.OnBarTwoResponse += foo_OnBarTwoResponse;
foo.FetchBarOne();
}
void foo_OnBarOneResponse(String response)
{
// Called successfully.
this.foo.FetchBarTwo();
}
Try using your field (which was not included in your example):
private Foo foo;
public Main()
{
this.foo = new Foo();
this.foo.OnBarOneResponse += foo_OnBarOneResponse;
this.foo.OnBarTwoResponse += foo_OnBarTwoResponse;
this.foo.FetchBarOne();
}
I've created my custom MessageBox using MessagePrompt from the Coding4Fun toolkit.
The problem occurs when I run ResetData_Click. I expected that after launching ComplexMessage.Show rest of the code inside ResetData_Click stops executing while ComplexMessage is open. As occurred it is completely different. All code is executed at once and it doesn't matter what user will chose in ComplexMessage because
if (ComplexMessage.Result)...
is already executed.
What should I do to make my ComplexMessage act like System.Windos.MessageBox? It means when MessageBox is called the parent's thread is waiting for the user's decision.
private void ResetData_Click(object sender, RoutedEventArgs e)
{
ComplexMessage.Show("You are about to delete all data", "Are you sure?", true);
if (ComplexMessage.Result)
{
DataControl.DataFileReset();
}
}
public class ComplexMessage
{
private static MessagePrompt messageprompt;
private static bool messageresult;
public static void Show(string message, string title, bool vibrate)
{
if (!(!(messageprompt == null) && messageprompt.IsOpen))
{
messageprompt = new MessagePrompt
{
Title = title,
Message = message
};
messageprompt.Completed += new EventHandler<PopUpEventArgs<string, PopUpResult>>(messageprompt_Completed);
messageprompt.IsCancelVisible = true;
messageprompt.Show();
if (vibrate) { Tools.VibrateMessage(); }
}
}
static void messageprompt_Completed(object sender, PopUpEventArgs<string, PopUpResult> e)
{
if (!e.PopUpResult.Equals(PopUpResult.Cancelled))
{
messageresult = true;
}
else
{
messageresult = false;
}
((MessagePrompt)sender).Completed -= messageprompt_Completed;
}
public static bool Result
{
get { return messageresult; }
}
}
Since you are displaying the MessageBox from a click event, you are running on the UI thread, which you don't want to freeze.
One option is to make ComplexMessage expose a static event, which it fires in messageprompt_Completed.
Then in ResetData_Click subscribe to the event prior to calling ComplexMessage.Show, and in the event handler, depending on the result, call DataControl.DataFileReset, and unsubscribe.
An alternative is to rethink making the members of ComplexMessage static, and instead to pass an "Action<bool> callback" parameter to the Show method, which you store away in a private member, and then invoke the callback in messageprompt_Completed.