from the class I am calling thread with
using (GeckoBrowserForm geckoBrowserForm = new GeckoBrowserForm(XulRunnerPath, propertyBag.ResponseUri.ToString()))
{
geckoBrowserForm.show();
}
that execute UI form OnLoad(EventArgs e)
protected override void OnLoad(EventArgs e)
{
GeckoWebBrowser m_GeckoWebBrowser = new GeckoWebBrowser();
m_GeckoWebBrowser.Invoke(new Action(() => {
m_GeckoWebBrowser.Parent = this;
m_GeckoWebBrowser.Dock = DockStyle.Fill;
m_GeckoWebBrowser.DocumentCompleted += (s, ee) =>
{
GeckoHtmlElement element = null;
var geckoDomElement = m_GeckoWebBrowser.Document.DocumentElement;
if (geckoDomElement != null && geckoDomElement is GeckoHtmlElement)
{
element = (GeckoHtmlElement)geckoDomElement;
DocumentDomHtml = element.InnerHtml;
}
if (m_Url.Equals(m_GeckoWebBrowser.Document.Url.ToString(), StringComparison.OrdinalIgnoreCase))
{
Done = true;
//m_GeckoWebBrowser.Navigate("about:blank");
//m_GeckoWebBrowser.Document.Cookie = "";
//m_GeckoWebBrowser.Stop();
}
};
m_GeckoWebBrowser.Navigate(m_Url);
}));
}
but problem is that code inside Invoke is never executed. Why? How can i execute code inside invoke?
Problem is that GeckoBrowser is a Windows Forms Control. A Control's properties and methods may be called only from the thread on which the Control was created. To do anything with a Control from another thread, you need to use the Invoke or BeginInvoke method, e.g. but how?
OnLoad is called on the UI-thread, therefore invoking is not necessary.
Besides that, you could also call these initialization steps in the constructor of the class.
Update:
To create it with a SynchronizationContext:
void ThreadFunc(object args)
{
...
var syncContext = (SynchronizationContext) args;
syncContext.Send(new SendOrPostCallback(_ => {
using (GeckoBrowserForm geckoBrowserForm = new GeckoBrowserForm(XulRunnerPath, propertyBag.ResponseUri.ToString()))
{
geckoBrowserForm.ShowDialog();
}),
null);
...
}
Related
No work await Task.Run():
private async void button2_Click(object sender, EventArgs e)
{
await Task.Run(() => {
monitor_r(label1);
});
}
protected async Task monitor_r(Label L1)
{
MessageBox.Show(L1.Name);
L1.ForeColor = Color.Blue;
L1.Text = "test";
}
These commands
MessageBox.Show(L1.Name);
and
L1.ForeColor = Color.Blue;
works fine but
L1.Text = "test";
does not work.
Can you help, why do not change a Label Text?
Try Control.Invoke: we should run Winform UI in the main thread only
protected async Task monitor_r(Label L1)
{
Action action = () =>
{
MessageBox.Show(L1.Name);
L1.ForeColor = Color.Blue;
L1.Text = "test";
};
if (L1.InvokeRequired)
L1.Invoke(action); // When in different thread
else
action(); // When in the main thread
}
If you're on debug mode, take a look at the output window. It should shows exception message something like this:
System.InvalidOperationException' in System.Windows.Forms.dll.
That because label1 accessed from a thread other than the thread it was created on. And it will causing invalid cross-thread operation.
You can solve this by using Control.Invoke as Dmitry Bychenko already mentioned. Here is simple extension to make thread-safe calls to Winforms Control.
public static void TryInvoke(this Control control, Action<Control> action)
{
if (control.InvokeRequired) control.Invoke(new Action(() => action(control)));
else action(control);
}
Sample usage
label1.TryInvoke(x => x.Text = "test");
label1.TryInvoke(x => x.ForeColor = Color.Blue);
Or
this.TryInvoke(x =>
{
label1.Text = "test";
label1.ForeColor = Color.Blue;
});
Secondly, since you don't await anything at monitor_r, i'd recommend to use void instead of async Task.
Even if you're await something at monitor_r you don't need
await Task.Run(() => {
monitor_r(label1);
});
..because monitor_r itself is a task. So just call await monitor_r(label1);
If you wish to have a separate thread, you can try this using BackgroundWorker. You can implement the ReportProgress if you have a loop.
private void button1_Click(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show(label1.Name);
label1.ForeColor = Color.Blue;
label1.Text = "test";
}
I have a program that runs and starts 2 long running tasks. One of the tasks is a web scraper in which I have to use the WebBrowser ActiveX control so that I can get the rendered page. In order to use the control I have to start a thread so that I can set the apartment state for the message loop. When I do this, the proogram works fine, or at least for the first page that is fetched. Subsequent pages/calls, the webbrowser times out and it's state seems to remain at "uninitialized". In tracing my code, I never see the "HandleDestroyed" fire for the WebClient.
What do I need to do to Properly Destroy the WebBrowser control and or my own class in order for it to be reused again.
public static string AXFetch(string url, string ua)
{
TestBrowser TB = new TestBrowser();
Thread th = new Thread(() => TB.MakeLiRequest(url,ua));
th.SetApartmentState(ApartmentState.STA);
th.Start();
th.Join(new TimeSpan(0, 0, 90)); //90 second timeout
SiteData = TB.DocumentText;
TB = null;
return SiteData;
}
class TestBrowser
{
public string DocumentText = "";
private bool DocCompleted = false;
public TestBrowser()
{
}
private void reset_fetch_status()
{
this.DocCompleted = false;
this.DocumentText = "";
}
public void MakeLiRequest(string url, string UA)
{
reset_fetch_status();
using (WebBrowser wb = new WebBrowser())
{
wb.Visible = false;
wb.AllowNavigation = true;
wb.ScriptErrorsSuppressed = true;
wb.DocumentCompleted += this.wb_DocumentCompleted;
wb.Navigate(url, "_self", null, "User-Agent: " + UA + "\r\n");
WaitForPage();
wb.Url = null;
wb.DocumentCompleted -= this.wb_DocumentCompleted;
}
}
private void HandleDestroyed(Object sender, EventArgs e)
{
//This never seems to fire, I don't knwo why
Logging.DoLog("You are in the Control.HandleDestroyed event.");
}
private bool WaitForPage()
{
int timer = 0;
while (this.DocCompleted == false)
{
Application.DoEvents();
System.Threading.Thread.Sleep(100);
++timer;
if (timer == (PageTimeOut * 10))
{
Console.WriteLine("WebBrowser Timeout has been reached");
Application.Exit();
return false;
}
}
return true;
}
private void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser wb = (WebBrowser)sender;
if (wb.ReadyState == WebBrowserReadyState.Complete)
{
this.DocumentText = wb.DocumentText;
this.DocCompleted = true;
}
}
}
On handle destroyed will only be called by the parent form.
If you were to try to access from the webbrowser control you would get this error:
Error 1 Cannot access protected member
'System.Windows.Forms.Control.OnHandleDestroyed(System.EventArgs)' via a
qualifier of type 'System.Windows.Forms.WebBrowser'; the qualifier must be of type 'stackoverflowpost47036339.Form1' (or derived from it)
Also you are not hooking it up. But since you haven't given your web browser any parent form, It can't be called. This is how you would hook it up to the parent form.
form1.HandleDestroyed += Form1_HandleDestroyed;
}
void Form1_HandleDestroyed(object sender, EventArgs e)
{
}
I have been looking around for about 3 hours and can not get this invoke to work. I need the invoke because whats calling it is in a different thread and says its unstable.
Here's what I'm calling (I call it like this textBox1_TextChanged(null, null);):
private void textBox1_TextChanged(object sender, EventArgs e)
{
if(this.InvokeRequired)
{
this.Invoke(this?WHAT GOES HERE, null); // I know it should be a delegate or something but I can't change this to that
}
else
{
string temp = "";
temp += TextToAdd;
textBox1.Text = "s";
}
}
You can use BeginInvoke to update the UI from other Thread.
if (this.InvokeRequired)
{
var action = new Action(() => textBox1.Text = "s");
this.BeginInvoke(action);
}
i call this after a button click:
FORM1-Code:
{
ProgressBar.Maximum = 500;
myArguments gifargs = new myArguments(); //class for passing arguments to the bgW
gifargs.InputFilePath = listBox1.SelectedItem.ToString(); // input filepath
gifargs.OutputFilePath = saveFileDialog1.FileName; //output filepath
backgroundWorker1.RunWorkerAsync(gifargs); // run bgW async with args
}
// here is bgW doWork
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
myArguments args = e.Argument as myArguments; //myArguments class
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
}
PictureHandler makeAnimatedGIf = new PictureHandler(); // creating new object
makeAnimatedGIf.imageGif(args.InputFilePath,args.OutputFilePath); //call method with args
makeAnimatedGIf.GifProgress += new PictureHandler.myprogressgetter(this.GifProgressF1);
//add the delegate
works perfect until here
this is my Callback function which should update bgW.ReportProgress
but it never gets there?!
private void GifProgressF1(int i)
{
System.Threading.Thread.Sleep(500);
backgroundWorker1.ReportProgress(i);
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressBar.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ProgressBar.Value = 0;
if (e.Cancelled)
{
MessageBox.Show("Process canceled!");
}
else
{
MessageBox.Show("Process complete!");
}
}
<----Picturehandler.cs-Code----->
//Delegate definition in my Picturehandler class
public delegate void myprogressgetter(int i);
public myprogressgetter GifProgress;
public void imageGif(string input, string output)
{
Process imagemagick = new Process();
imagemagick.StartInfo.FileName = "convert.exe";
imagemagick.StartInfo.Arguments = "-monitor -delay 1 " + input + " +map " + output;
imagemagick.EnableRaisingEvents = false;
imagemagick.StartInfo.UseShellExecute = false;
imagemagick.StartInfo.CreateNoWindow = true;
imagemagick.StartInfo.RedirectStandardOutput = true;
imagemagick.StartInfo.RedirectStandardError = true;
imagemagick.Start();
StreamReader ima = imagemagick.StandardError;
bool assign2 = false;
do
{
string consolausgabe = ima.ReadLine();
if (consolausgabe.Contains("Reduce") == true)
{
assign2 = true;
}
gifprocess(consolausgabe, assign2);
} while (!ima.EndOfStream);
imagemagick.WaitForExit();
imagemagick.Close();
}
private void gifprocess(string cline, bool zähl)
{
if (cline.Contains("Load"))
{
string a1 = cline;
string[] a11 = a1.Split(new char[] { ':', ',' });
string a12 = a11[3];
string[] a13 = a12.Split(new char[] { '%' });
int load1 = Convert.ToInt32(a13[0]);
GifProgress(load1;) //<<<<<------------- this will give me an exception
// Visual Studio says GifProgress = null in Autos
}
now if i call GifProgress(100) or any other integer, I get exception(Object reference not set to an instance of an object.), progressbar gets never updated.
The progress information from the picturehandler class wont get to the UI, I tried for 2 days now.
I use same code to get textbox.text from form2 and callback function works just fine.
WorkerReportProgress = TRUE.
Typically, your DoWork method would have a loop in it. Each iteration of the loop could finish with a call to ReportProgress, which would cause the stuff in ProgressChanged to run.
Since you're just running a few lines of code, use the RunWorkerCompleted to set the progress indicator and forget ReportProgress altogether.
Here's a tutorial I used to understand the BackgroundWorker better, if it helps...
Unless maybe you're doing what you're doing because your background worker thread exits before makeAnimatedGIf.imageGif is finished doing whatever it does...
Make sure your BackgroundWorker's WorkerReportsProgress property is set to true. Default is false
Make sure to set WorkerReportsProgress to true and use Invoke() to update the progressbar from another thread.
The function that you expect to be called, GifProgressF1 is only referenced as a call back for an instance of `PictureHandler' class. Where and how this call back is called in entirely up to that class then. However from your description it is not clear where this class comes from or what it does. I'd suggest referring either to the class documentation or source code, to find exactly when this callback is supposed to be called and going from there.
I have created a thread to perform certain functionality in my application and while performing it I want to update the label in the main form of the application which is visible to user.
I tried to return the string data through the function which I am calling usinag seprate thread but it does not work.
Please let me know if there is any solution to update the label text while performing an activity using thread.
class e2ertaData : e2erta1
{
public void rsData()
{
network networkDetails = new network();
csv csvFile = new csv();
ftpFile ftpData = new ftpFile();
//Host Geo Data
string getIP = networkDetails.GetIP();
string[] hostData = getIP.Split('~');
GeoIP geoIPReq = new GeoIP();
GeoIpData geoIPReqData = new GeoIpData();
geoIPReqData = geoIPReq.GetMy();
if (geoIPReqData.KeyValue["Error"].ToString() == "NO")
{
//Reading server names from XML file
XmlDocument thisXmlDoc = new XmlDocument();
thisXmlDoc.LoadXml(ftpData.getConfigFile("server.xml"));
XmlNodeList xnList = thisXmlDoc.SelectNodes("/servers/server");
//updating label in e2erta1
this.l1.Text = "daaaaaaaaaaa";
this.l1.Visible = true;
this.l1.Refresh();
foreach (XmlNode xn in xnList)
{
string rtNote = "";
string requestedServer = xn["sname"].InnerText;
string rtGet = networkDetails.GetRT(requestedServer);
if (rtGet.Contains("Exception"))
{
rtNote = rtGet;
//MessageBox.Show(rtNote);
}
try
{
var row = new List<string> { rtGet, rtNote };
ftpData.addToCSVFile(row);
}
catch (Exception c)
{
MessageBox.Show(c.ToString());
}
}
}
else
{
MessageBox.Show("Geo data : " + geoIPReqData.KeyValue["Error"].ToString());
}
//return null;
}
}
Thanks,
Naveed
Also consider using BackgroundWorker component.
Drag BackgroundWorker from ToolBox to your form
Set backgroundworker's property WorkerReportsProgress to true
Subscribe to backgroundworker's event DoWork
Subscribe to backgroundworker's event ProgressChanged
Inside DoWork event handler run all what you do in your thread, and call ReportProgress method to pass progress to your form:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// reading server names from XML file
for (int i = 0; i < xnList.Count; i++)
{
XmlNode xn = xnList[i];
// process node
// report percentage to UI thread
int percentProgress = (i+1)*100/xnList.Count;
backgroundWorker1.ReportProgress(percentProgress);
}
}
Inside ReportProgress event handler simply assign value to label:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = e.ProgressPercentage.ToString();
}
To start background processing call backgroundWorker1.RunWorkerAsync();
UPDATE: Your code is not working, because controls could be updated only from thread which created them (UI thread). So you should use Invoke to execute update functionality on UI thread. Example and description you can find here.
Use this from your thread:
this.Invoke((MethodInvoker)delegate
{
label.Text = "...";
});
Edit:
You can also test the IsHandleCreated property before using Invoke:
private void UpdateLabel(string text)
{
if (this.IsHandleCreated)
{
this.Invoke((MethodInvoker)delegate
{
label.Text = text;
});
}
else
{
label.Text = text;
}
}