Updating a dialog from another form - c#

I know there are a lot of similar questions out there and I have read through a lot of them. Unfortunately, I still couldn't solve my problem after reading through them - however I am relatively new to C#. According to docs the problem of not being thread safe results in an InvalidOperationException when using the debugger. This is not the case in my problem.
I've recreated the problem with a simple raw test class to concentrate on my problem.
The main form is supposed to show a kind of a progress dialog.
public partial class ImportStatusDialog : Form
{
public ImportStatusDialog()
{
InitializeComponent();
}
public void updateFileStatus(string path)
{
t_filename.Text = path;
}
public void updatePrintStatus()
{
t_printed.Text = "sent to printer";
}
public void updateImportStatus(string clientName)
{
t_client.Text = clientName;
}
public void updateArchiveStatus()
{
t_archived.Text = "archived";
}
}
When that code is called without any Invoke() from the main form:
private void button1_Click(object sender, EventArgs e)
{
ImportStatusDialog nDialog = new ImportStatusDialog();
nDialog.Show();
nDialog.updateFileStatus("test");
Thread.Sleep(1000);
nDialog.updateImportStatus("TestClient");
Thread.Sleep(1000);
nDialog.updatePrintStatus();
Thread.Sleep(1000);
nDialog.updateArchiveStatus();
Thread.Sleep(1000);
nDialog.Close();
}
And even when I call it like this:
private void button3_Click(object sender, EventArgs e)
{
ImportStatusDialog nDialog = new ImportStatusDialog();
nDialog.Show();
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateFileStatus("Test");
});
}
else
{
nDialog.updateFileStatus("Test");
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updatePrintStatus();
});
}
else
{
nDialog.updatePrintStatus();
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateImportStatus("cName");
});
}
else
{
nDialog.updateImportStatus("cName");
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateArchiveStatus();
});
}
else
{
nDialog.updateArchiveStatus();
}
Thread.Sleep(1000);
nDialog.Close();
}
the dialog which looks like this in the designer (in my example)
will be displayed like that:
When I use ShowDialog() instead of Show() the dialog displays correnctly, but as the API Doc points out
You can use this method to display a modal dialog box in your
application. When this method is called, the code following it is not
executed until after the dialog box is closed
which is not what I want, especially as it would mean that the dialog updating would only happen after it has been closed again.
What am I doing wrong here? This seems to be a trivial problem and yet the solution evades me. Please keep in mind that I am new to C# GUI programming.
Also, I would like to ask what would be the right place for using the Invoke()? Do Would you use it in the main form when calling the dialog methods or rather in the dialog update methods itself?

It doesn't look like you're using multiple threads here, so the invoke stuff is not required. Invoke is only needed for cross-thread calls - you are creating multiple forms, but all your code is running in the same thread.
If you're doing your work in the main UI thread (as implied by the code being in a button click event), then just call Application.DoEvents() periodically to allow the progress form to refresh.
A better solution would be to use a BackgroundWorker for your work, and have it report its progress periodically.
Then you can update the progress form in the BackgroundWorker's ProgressChanged event (which will be executed in the main thread, so you still don't have to invoke anything).

Related

Cross-thread operation not valid...Invoke and delegate seems useless

I have a issue with thread, I've searched for a few days but still cannot solve it..
Due to some reason, I customize a progress form and use it in threads.
I tried to write all functions inside the progress form so that they are wrapped by Invoke and delegate. Unfortunately, this code is not working properly since this.InvokeRequired is returning false when I expected it to return true.
The problem is, when I execute the program, sometimes it throw an exception:
Cross-thread operation not valid: Control 'FormProgress' accessed from a thread other than the thread it was create on.
Here's the code of progress form.
I've wrapped all functions with Invoke and delegate.
public partial class FormProgress : Form
{
public FormProgress()
{
InitializeComponent();
}
public void SetStatusLabelText(string text)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker) delegate
{
label1.Text = text;
});
}
else
{
// exception thrown here
label1.Text = text;
}
}
public void SetDialogResult(DialogResult dialogResult)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
if (DialogResult == DialogResult.None)
this.DialogResult = dialogResult;
});
}
else
{
if (DialogResult == DialogResult.None)
this.DialogResult = dialogResult;
}
}
}
Here's the code of thread, the exception throws when I click button1
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
for (int i=0; i<100; i++)
ProgressTest();
}
private void ProgressTest()
{
FormProgress dialog = new FormProgress();
{
Thread threadTest = new Thread(delegate ()
{
dialog.SetStatusLabelText("initial....(1)");
Thread.Sleep(50);
dialog.SetStatusLabelText("initial....(2)");
Thread.Sleep(50);
dialog.SetStatusLabelText("initial....(3)");
Thread.Sleep(50);
dialog.SetDialogResult(DialogResult.OK);
});
threadTest.Name = "ThreadTest";
threadTest.Start();
if (dialog.ShowDialog() == DialogResult.Cancel)
{
if (threadTest.IsAlive)
threadTest.Abort();
}
threadTest.Join();
}
}
}
As per the docs:
If the control's handle does not yet exist, InvokeRequired searches up
the control's parent chain until it finds a control or form that does
have a window handle. If no appropriate handle can be found, the
InvokeRequired method returns false.
This means that InvokeRequired can return false if Invoke is not
required (the call occurs on the same thread), or if the control was
created on a different thread but the control's handle has not yet
been created.
In the case where the control's handle has not yet been created, you
should not simply call properties, methods, or events on the control.
This might cause the control's handle to be created on the background
thread, isolating the control on a thread without a message pump and
making the application unstable.
You can protect against this case by also checking the value of
IsHandleCreated when InvokeRequired returns false on a background
thread. If the control handle has not yet been created, you must wait
until it has been created before calling Invoke or BeginInvoke.
Typically, this happens only if a background thread is created in the
constructor of the primary form for the application (as in
Application.Run(new MainForm()), before the form has been shown or
Application.Run has been called.
The issue you have is that some of your InvokeRequired calls may be occurring before the form has been shown. This is because you are starting your new thread before calling dialog.ShowDialog(). Note, as is common with race conditions, the problem won't always occur - just sometimes.
As per above, you may want to consider checking IsHandleCreated before executing the logic in your else blocks (after checking InvokeRequired) to protect against this possibility. Alternatively, rethink your entire strategy around the progress form.
do changes with controls inside if (this.InvokeRequired) block
remove the else block staying after if (this.InvokeRequired)
public void SetStatusLabelText(string text)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker) delegate
{
label1.Text = text;
});
}
}
public void SetDialogResult(DialogResult dialogResult)
{
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
if (DialogResult == DialogResult.None)
this.DialogResult = dialogResult;
});
}
}
let's consider method ProgressTest(), what happaning:
after threadTest.Start() has been called , the threadTest method starts execution of his work item in a new thread
after dialog.ShowDialog() the GUI thread become blocked , it makes this.InvokeRequired = false
at the same time threadTest keep working and when threadTest try to execute
else
{
label1.Text = text;
}
label1.Text setter is called from NONE GUI thread (it is called from "ThreadTest" thread), that's why you get exception
It should be noted that dialog.SetStatusLabelText("initial....") which supposed to be called 300 times , actually will be called less then 300

Winform - Continue from a ShowDialog without hiding the window

In a multithreading environment.
I'm using ShowDialog in a progress form to block all user activities on other forms.
After finish part of the process I hide the progress form, I do some tasks and I show (ShowDialog) it again.
Everything's works fine but the progress form flickers.
Is there a way to continue from a ShowDialog without hiding the window or, probably better, is there a way to transform a Show to a ShowDialog and back again?
EDIT
My code is similar to this
class frmProgress : Form
{
// Standard stuff
public void DoSomeWorks()
{
async Task.Run(() => RunWork1());
ShowDialog();
if (_iLikeTheResultOfWork1)
{
async Task.Run(() => RunWork2());
ShowDialog();
}
}
void RunWork1()
{
// Do a lot of things including update UI
Hide();
}
void RunWork2()
{
// Do a lot of things including update UI
Hide();
}
}
EDIT 2
Thanks to everyone for the answers and the suggestions.
At the end I adopted the solution to Group some small works in one bigger work that I use in UI while in unit tests I still test the small works.
Actually it was not the solution I was looking for, I hoped to find a solution based on Handling the message pump or doing something similar (see System.Windows.Forms.Application source code starting from RunDialog(.) and all the called methods where I got lost before posting this question).
You can't do that. At least there is not an easy way that I am aware of. One way to tackle that is to change your code as follows:
//C#-ish pseudocode based on OPs example; doesn't compile
class frmProgress : Form
{
// Standard stuff
public void DoSomeWorks()
{
async Task.Run(() => RunWork1());
ShowDialog();
}
void RunWork1()
{
// Do a lot of things including update UI
if (_iLikeTheResultOfWork1)
{
async Task.Run(() => RunWork2());
}
else
{
Hide();
}
}
void RunWork2()
{
// Do a lot of things including update UI
Hide();
}
}
EDIT For those complaining the code doesn't compile, they are right. But this is the best the OP will get with that code sample and that's the reason I guess he is mercilessly downvoted.
But to make the answer more relevant to others, my suggestion is don't hide the progress form between the the 2 tasks, hide it when you are sure that the tasks have ended. All these by respecting the threading model of the context you are working on, something the OP's code doesn't do. Manipulating the UI in Winforms from methods that will eventually run in other threads won't work.
I suppose you have some methods like following:
void Task1()
{
Debug.WriteLine("Task1 Started");
System.Threading.Thread.Sleep(5000);
Debug.WriteLine("Task1 Finished");
}
void Task2()
{
Debug.WriteLine("Task2 Started");
System.Threading.Thread.Sleep(3000);
Debug.WriteLine("Task2 Finished");
}
If you are going to run them in parallel:
private async void button1_Click(object sender, EventArgs e)
{
this.Enabled = false;
//Show a loading image
await Task.WhenAll(new Task[] {
Task.Run(()=>Task1()),
Task.Run(()=>Task2()),
});
//Hide the loading image
this.Enabled = true;
}
If you are going to run them one by one:
private async void button1_Click(object sender, EventArgs e)
{
this.Enabled = false;
//Show a loading image
await Task.Run(()=>Task1());
await Task.Run(()=>Task2());
//Hide the loading image
this.Enabled = true;
}
Referencing Async/Await - Best Practices in Asynchronous Programming
I advise you take advantage of async event handlers and tasks to allow for non blocking calls while the form is in modal via show dialog
class frmProgress : Windows.Form {
// Standard stuff
public void DoSomeWorks() {
Work -= OnWork;
Work += OnWork;
Work(this, EventArgs.Empty);//raise the event and do work on other thread
ShowDialog();
}
private CancellationTokenSource cancelSource;
private event EventHandler Work = delegate { };
private async void OnWork(object sender, EventArgs e) {
Work -= OnWork; //unsubscribe
cancelSource = new CancellationTokenSource(); //to allow cancellation.
var _iLikeTheResultOfWork1 = await RunWork1Async(cancelSource.Token);
if (_iLikeTheResultOfWork1) {
await RunWork2Async(cancelSource.Token);
}
DialogResult = DialogResult.OK; //just an example
}
Task<bool> RunWork1Async(CancellationToken cancelToken) {
// Do a lot of things including update UI
//if while working cancel called
if (cancelToken.IsCancellationRequested) {
return Task.FromResult(false);
}
//Do more things
//return true if successful
return Task.FromResult(true);
}
Task<bool> RunWork2Async(CancellationToken cancelToken) {
// Do a lot of things including update UI
//if while working cancel called
if (cancelToken.IsCancellationRequested) {
return Task.FromResult(false);
}
//Do more things
//return true if successful
return Task.FromResult(true);
}
}
Note the use of cancellation token to allow tasks to be cancelled as needed.
The UI is now unblocked and the async functions can continue the work. No flickering as the form remain shown without any interruptions.
I could be missunderstood, here what I did, handleDialog with wndProc. I created an extra form and used Show(); method. After I showed form, async tasks are started. After I showed the form, I handled that form by wndProc.
protected override void WndProc(ref Message m) {
if((f2.IsDisposed || !f2.Visible)) {
foreach(var control in controlList) {
control.Enabled = true; // enable all controls or other logic
}
}
if(m.Msg == 0x18 && !f2.IsDisposed) { // notify dialog opens and double checks dialog's situation
foreach(var control in controlList.Where(ctrl => ctrl.Name != "button1")) {
control.Enabled = false; // disable except cancel button
}
}
base.WndProc(ref m);
}
Hope helps,
From the code you posted, you are calling Hide() at the end of your RunWork() methods, and then ShowDialog() right afterwards. If I understand correctly, you want to call Hide() first inside your RunWork() methods, which makes the main UI window accessable while the UI updates are occurring. After everything finishes, then the ShowDialog() method occurs and blocks the main UI thread again.
class frmProgress : Form
{
public bool _iLikeTheResultOfWork1 = true;
// Note: changed to async method and now awaiting the task
public async void DoSomeWorks()
{
await Task.Run(() => RunWork1());
ShowDialog();
if (_iLikeTheResultOfWork1)
{
await Task.Run(() => RunWork2());
ShowDialog();
}
}
// Hide window and wait 5 seconds. Note that the main window is active during this 5 seconds.
// ShowDialog() is called again right after this, so the dialog becomes modal again.
void RunWork1()
{
Hide();
Task.Delay(5000).Wait();
// Do a lot of things including update UI
}
void RunWork2()
{
Hide();
Task.Delay(5000).Wait();
// Do a lot of things including update UI
}
}
My code above will call RunWork1(), which hides the dialog for 5 seconds. During this time the main UI window will be active. Afterwards, the method returns and ShowDialog() is called. Close that window to call RunWork2() and start the process over again.
Is this what you are attempting to achieve? I just tested this code and it works with no visual "flickers". If this is what you want, does the "flickering" occur with this method?
"the form will always be closed when it leaves its modal message loop, the one that was started by ShowDialog().  The best you can do is simulate modality, display the form with Show() and disable all other windows."
https://social.msdn.microsoft.com/Forums/sharepoint/en-US/3f6c57a1-92fd-49c5-9f46-9454df80788c/possible-to-change-modality-of-a-dialog-box-at-runtime?forum=winforms
So you could enumerate all your app's Windows and enable/disable them when needed, plus set your dialog window to be always on top when simulating modality. I think always on top means it comes above all windows in the OS though, so you may need a timer instead to bring your window to the front (among your app windows) if it is set to simulate modality

Closing the Form opened in another thread

I'm getting some troubles with my Winforms C# app.
I wish to make form named Popup closing after some operations in main thread are done. The problem is an exception caused by cross-thread form closing.
private void loginButton_Click(object sender, EventArgs e)
{
LoginProcess.Start(); // Running Form.show() in new thread
ActiveAcc.IsValid = false;
ActiveAcc.Username = userBox.Text;
try
{
LoginCheck(userBox.Text, passBox.Text);
}
catch (IOException)
{
MessageBox.Show("..");
return;
}
catch (SocketException)
{
MessageBox.Show("..");
return;
}
if (ActiveAcc.IsValid)
{
MessageBox.Show("..");
Close();
}
else
{
Popup.Close(); // Error caused by closing form from different thread
MessageBox.Show("");
}
}
public Login() // 'Main' form constructor
{
InitializeComponent();
ActiveAcc = new Account();
Popup = new LoginWaiter();
LoginProcess = new Thread(Popup.Show); //Popup is an ordinary Form
}
I've been trying to use various tools such as LoginProcess.Abort() or Popup.Dispose() to make it work properly, but even if app is working on runtime environment its still unstable due to Exceptions which are thrown.
I would be grateful for any help, and I am sorry for ambiguities in issue describing.
Why don't you let the UI thread do UI stuff like opening and closing Forms, and spawn the other thread (or background worker, or async task) to do the other stuff?
IMO, having other threads attempt to interact with elements on the UI thread (e.g., have a background thread directly set the text of a label or some such) is asking for heartache.
If you simply must keep your code as is, here is a fairly simple thing you could do. In Popup, add a static bool that defaults to true. Also in Popup, add a timer task that once every X milliseconds checks the status of that boolean. If it finds that the value has been set to false, let Popup tell itself to close within that timer tick.
I'm not crazy about this design, but it could look something like:
public partial class Popup : Form
{
public static bool StayVisible { get; set; }
private System.Windows.Forms.Timer timer1;
public Popup()
{
StayVisible = true;
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
if (!StayVisible) this.Close();
}
}
Then, from another thread, when you want Popup to close, call
Popup.StayVisible = false;
Better yet, you would fire an event that Popup would receive so that it could close itself. Since you intend to use multiple threads, you'll have to deal with raising events cross-thread.

Thread-safe update TextBox from another class

I have a form called 'Home' and a class called 'Server', which is threaded. Home has a TextBox that I'd like to append as things happen in Server. What is easiest thread safe way to append the text box that's not in the same class? I see a lot of talk on this subject but nothing seems to answer my question. The other solutions also contain a lot of code for something that seems simple.
I start my thread form Home like so:
public void StartThread()
{
Server s = new Server();
Thread t = new Thread(s.DoWork);
t.Start();
}
class Server
{
public void DoWork()
{
while (!_shouldStop)
{
StartServer();
}
}
public void RequestStop()
{
_shouldStop = true;
}
private volatile bool _shouldStop;
internal void StartServer()
{
try
{
// Server Stuff
// Something happens here and I want to append a string to the text box in Home.
}
catch (Exception)
{
//Exception, append text box with some other string.
}
}
Have the form create a Progress<string> instace,
Attach a handler to the progress changed event that updates the UI appropriately.
Have the worker accept an IProgress<string> instance that it reports progress to.
The Progress class will handle marshaling the code to the UI thread.
This ensures proper separation of UI from non-UI code.
I'm not sure if this is what you mean (Preforming cross threaded operations I assume?) but...
textBox1.Invoke(new MethodInvoker(delegate { textBox1.AppendText("Hi"); }));
This would be to append some text to your textbox safely, you would just have to add the if statement and such.

Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on

Im pretty new to C# but I have been playing around with it to learn. So far I have a nice app that does things like starts up the screen saver and controls the windows system volume.
However I'm having some trouble and I'm not sure what is wrong. I have already gone through a bunch of similar questions on the site but I don't think any of them apply to my specific case.
So I wanted to control the app from the web. My plan is to have the app check a webpage on my site for a command every couple seconds. Depending on what the site returns the app will do different things(mute, vol up, etc.) and reset the command on the website to blank. The website command part is all done in PHP and is quite simple, there is no problem with that part of it.
I created a button that calls a function that checks the page and performs the action. It worked fine. But when I tried to make it automatically check the site I'm getting errors. I'm pretty sure it is because I moved the check and perform action to a new thread. So lets move on to the code. These are not the full files but what I think you need to know to help. If you need anything more just let me know.
Form1.cs
public Form1(Boolean init = true)
{
if (init)
{
InitializeComponent(); //initialize UI
startWebMonitor(); //starts web monitor thread for remote web commands
}
}
private void startWebMonitor()
{
Thread t = new Thread(WebMonitor.doWork);
t.Start();
}
public IntPtr getWindowHandle()
{
return this.Handle;
}
WebMonitor.cs
public static void doWork()
{
while(true)
{
checkForUpdate();
Thread.Sleep(1000);
}
}
private static void checkForUpdate()
{
lastCommand = getLastCommand();
if (lastCommand.Equals(""))
{
//No Update
}
else
{
processCommand(lastCommand);
}
}
public static void processCommand(String command)
{
if(command.Equals("mute"))
{
VolumeCtrl.mute(Program.form1.getWindowHandle());
}
HTTPGet req2 = new HTTPGet();
req2.Request("http://mywebsite.com/commands.php?do=clearcommand");
}
VolumeCtrl.cs
private static IntPtr getWindowHandle()
{
Form1 form1 = new Form1(false); //false = do not initialize UI again
return form1.getWindowHandle();
}
public static void mute(IntPtr handle)
{
SendMessageW(getWindowHandle(), WM_APPCOMMAND, getWindowHandle(), (IntPtr)APPCOMMAND_VOLUME_MUTE);
}
Alright so basically the mute function requires the window handle in order to work. But when I try to get the window handle from the new thread it throws the error:
Cross-thread operation not valid: Control 'Form1' accessed from a
thread other than the thread it was created on.
So how do I get around this? I have read other answers on here saying you need to use Invoke or Delegate but I'm not sure how those work or where to use them. I tried to follow one of the answers but I got even more errors.
In this case, write invoke operation inside of method. Then you can access both from control's thread and other threads.
delegate IntPtr GetWindowHandleDelegate();
private IntPtr GetWindowHandle() {
if (this.InvokeRequired) {
return (IntPtr)this.Invoke((GetWindowHandleDelegate)delegate() {
return GetWindowHandle();
});
}
return this.Handle;
}
or, if you want to avoid delegate hell, you could write in more few code with built-in delegate and lambda.
private IntPtr GetWindowHandle() {
if (this.InvokeRequired)
return (IntPtr)this.Invoke((Func<IntPtr>)(GetWindowHandle));
return this.Handle;
}
Try this code it will surely help you
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (s, e) => { };
bw.RunWorkerCompleted += (s, e) =>
{
//Call you windowsform method or anything which you want to do
};
bw.RunWorkerAsync();

Categories

Resources