Dispatcher.BeginInvoke to call another window in c# - c#

In my wpf application i have made a button click event as seperate thread and run as background process so that the UI is responsive to the user. Code as below,
private void btn_convert_Click(object sender, RoutedEventArgs e)
{
//Makes the conversion process as background task which
//makes the UI responsive to the user.
Thread thread = new Thread(new ThreadStart(WorkerMethod));
thread.SetApartmentState(ApartmentState.MTA);
thread.IsBackground = true;
thread.Start();
}
With in the WorkerMethod I have an option to change the filename which i am providing user a separate window.For this action I am using Dispatcher method as below,
if (MessageBox.Show("Do you want to set filename?",
"Information", MessageBoxButton.YesNo, MessageBoxImage.Asterisk) ==
MessageBoxResult.Yes)
{
Action showOutput = () =>
{
BlueBeamConversion.SetOutput _setOutput =
new BlueBeamConversion.SetOutput();
_setOutput.ShowDialog();
};
Dispatcher.BeginInvoke(showOutput);
if (String.IsNullOrEmpty(MainWindow.destinationFileName))
return;
where destinationFileName will be set in SetOutput window. Now come to my issue, when above code executes SetOutput window shows up and doesn't wait until i set the filename. Before setting the filename it comes to the below code,
if (String.IsNullOrEmpty(MainWindow.destinationFileName))
return;
How can i hold until i click ok button in setoutput window.Any suggessions are most welcome.
I used dispatcher.Invoke instead of BeginInvoke. Now it holds the window and takes new name. But when continues the code in workmethod in a certain line it exits the application itself, please fined the code bekow,
if (MessageBox.Show("Do you want to set filename?", "Information", MessageBoxButton.YesNo, MessageBoxImage.Asterisk) == MessageBoxResult.Yes)
{
Action showOutput = () =>
{ BlueBeamConversion.SetOutput _setOutput = new BlueBeamConversion.SetOutput(); _setOutput.ShowDialog(); };
Dispatcher.Invoke(showOutput);
for (int i = 0; i < _listFiles.Items.Count; i++)--- here it exits
{--------- }
Regards
sangeetha

use ShowDialog() instead of Show() and store the output in the DialogResult
var result = _setOutput.ShowDialog();

You can use Invoke instead of BeginInvoke :
//Dispatcher.BeginInvoke(showOutput);
Dispatcher.Invoke(showOutput);

if you use ShowDialog, you can store the value in a public property of your second window and can access it in a way like this:
Form2 form2 = new Form2();
if (form2.ShowDialog() == DialogResult.OK)
{
if (form2.ReturnData == "myResult")
...
}

while you are using window.show() method in you action you will not receive any result from show method insteed you have to call the show dialog method of window which will inforce the GUI to hold untill the dialog window is closed and after it you will be able to recive the data from you dialog windo.
Action showOutput = () =>
{ BlueBeamConversion.SetOutput _setOutput = new BlueBeamConversion.SetOutput(); _setOutput.ShowDialog(); };
Dispatcher.BeginInvoke(showOutput);
and the other hand you can wait for the thread to be complete first and till you can wait. this approch will also work for you. the dispatcher.Invoke will help you out.or you can try DispatcherOperation here.
try with below changed code.
DispatcherOperation op = Dispatcher.BeginInvoke(showOutput);
op.Wait();
if (String.IsNullOrEmpty(output))
return;

Related

How to open a second form on the same thread of the first form?

I have a WinForms client with 2 forms. The first form calls a separate class uses 'SignalR' for notifications from a WebApi. The setup of the hub proxy for a particular message in the class is:
onResult = myProxy.On<int>("Result", (id) =>
{
Result?.Invoke(this, new ResultEventArgs(id));
});
In the first form I subscribe to the Result event and then I have:
private void OnResult(object sender, ResultEventArgs e)
{
using (var form = new SecondForm(e))
{
var dialogResult = form.ShowDialog(this);
if (dialogResult == DialogResult.Cancel)
return;
}
}
I get a CrossThreadException on var dialogResult = form.ShowDialog(this); The first form (this) is opened on the UI thread. The second form is being opened in same thread as the SignalR class uses.
I do need to open the second form using ShowDialog(this) as I need it to be the topmost form in the app.
Is there a workaround for this problem? Is it possible to open the second form in the UI thread too?
UPDATE:
A workaround which works to do:
form.TopMost = true;
form.StartPosition = FormStartPosition.CenterScreen;
The only drawback is that the form is the topmost form on the desktop, not only within the application.
You can not create, change, or access any UI control on any thread other than the single UI thread. You must call .Invoke(...) on an existing control to marshall calls from another thread back to the UI.
Try this:
private void OnResult(object sender, ResultEventArgs e)
{
Action x = () =>
{
using (var form = new SecondForm(e))
{
var dialogResult = form.ShowDialog(this);
if (dialogResult == DialogResult.Cancel)
return;
}
};
this.Invoke(x);
}

Why does my SplashScreen Not show a Label (Text)

I have a WinForm called SpalshScreen.cs with a simple label with the Text property is set to "Data Loading...". The label is centered in the Form. I also have a public method called DoClose() method defined.
My MainForm.Form_Load method contains:
Hide();
SplashScreen form = new SplashScreen();
form.Show();
// Simulate Doing Database Table Load(s)
Thread.Sleep(5000);
form.DoClose();
Show();
However when I run, my Splash does appear but, where the Label Text is suppose to be it only shows a Light Colored Box.
If I change form.Show(); to form.ShowDialog(); the text appears correctly but the main loop pauses until I close the Splash Window.
After a bunch of trial and error... The trick is to Not block the UI Thread as #Servy said.
The Form_Load method needed to change to:
Hide();
Splash.Show();
// Everything after this line must be Non UI Thread Blocking
Task task = new Task(LoadDataAsync);
task.Start();
task.Wait();
Splash.DoClose();
Show();
And I created a LoadDataAsync Method to take care of everything else:
private async void LoadDataAsync()
{
await Context.Employees.LoadAsync();
await Context.Customers.LoadAsync();
// The Invoke/Action Wrapper Keeps Modification of UI Elements from
// complaining about what thread they are on.
if (EmployeeDataGridView.InvokeRequired)
{
Action act = () => EmployeeBindingSource.DataSource = Context.Employees.Local.ToBindingList();
EmployeeDataGridView.Invoke(act);
}
if (CustomerComboBox.InvokeRequired)
{
Action act = () =>
{
CustomerBindingSource.DataSource = GetCustomerList();
CustomerComboBox.SelectedIndex = -1;
};
CustomerComboBox.Invoke(act);
}
}
I also set any of the private fields and private methods I was using to static.
Use timer inside your splashscreen form instead of thread.sleep ( for example close splash screen after 5 seconds), and set closed event of it.
var form = new SplashScreen();
form.Closed += (s,e)=>{
Show();
}
form.Show();

How to implement a popup window with a counter

I would like to implement a simple popup window in Windows Forms, which will show a simple timer to the user while some slow-running process is executing. The premise is simple; show to the user that something is indeed going on and the application is not frozen. Note that this slow-running process is not a loop, nor is it something that I can tap into.
What I want is a simple popup window, showing some message along the lines "Elapsed time: x seconds", where x is incremented every second.
The basic concept is the following:
public void test()
{
//Some code which does stuff
//Popup window with counter
//Perform long running process
//Close popup window with counter
//Some other code which does other stuff
}
I tried to do it using various ways, including background workers, threads, and of course timers. But I did not manage to make it work as I wanted. And I would prefer not to post any of my code so as not to "lead" the responses to a specific way of doing this.
So what would be the best way to do this work?
Thanks.
UPDATE:
In reply to some comments, since I cannot paste any code in the replies section, I'm editing my original question to accomodate this. One of the implementations that I tried is to spawn the popup window in a separate thread. Although I got no runtime errors, the popup window did not refresh correctly. It indeed poped-up, but no text would show within it, and the counter would not refresh. Here's the code:
private void test()
{
frmProgressTimer ofrmProgressTimer = new frmProgressTimer(); //Instance of popup Form
System.Threading.Tasks.Task loadTask = new System.Threading.Tasks.Task(() => ProgressTimer(ofrmProgressTimer));
loadTask.Start();
//Perform long running process
System.Threading.Tasks.Task cwt = loadTask.ContinueWith(task => EndProgressTimer(ofrmProgressTimer));
}
private void ProgressTimer(frmProgressTimer ofrmProgressTimer)
{
ofrmProgressTimer.Show();
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.startTimer();
}));
}
private void EndProgressTimer(frmProgressTimer ofrmProgressTimer)
{
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.stopTimer();
ofrmProgressTimer.Close();
}));
}
And here's my popup form code:
public partial class frmProgressTimer : Form
{
private int counter = 0;
private Timer timer1;
public frmProgressTimer()
{
InitializeComponent();
timer1 = new Timer();
timer1.Interval = 1000;
timer1.Tick += new EventHandler(timer1_Tick);
}
public void startTimer()
{
timer1.Start();
}
public void stopTimer()
{
timer1.Stop();
}
private void timer1_Tick(object sender, EventArgs e)
{
counter += 1;
labelText.Text = counter.ToString();
}
}
This is actually quite easy to do. Create your dialog, define your long running operation to take place in a non-UI thread when it is shown, add a continuation to that operation which closes the dialog when the task finishes, and then show the dialog.
MyDialog dialog = new MyDialog();
dialog.Shown += async (sender, args) =>
{
await Task.Run(() => DoLongRunningWork());
dialog.Close();
};
dialog.ShowDialog();
The code to have the ticking over time should be entirely contained within the dialog, and based on the question it seems you already have that well under control with a simple Timer.
Make a new form, which will pop up, and show a timer. That way it won't be interrupted with all the work on your main form, and the timer will work continuously.
Remember when showing a new from to use newForm.ShowDialog() not newForm.Show(). Your can google the differences
I would simply start your work on a separate thread. Launch a modal form with your timer output. To display the timer use an actual timer instance set to update every second. When the timer event fire update your dialog.
Finally once you're thread completes close the dialog so your main form is active again.
First of all you need to make it not closeable by the user (as if modal dialogs weren't annoying enough) but closeable by your code. You could accomplish this by subscribing to the FormClosing event of the form. Let's say your popup form's name is Form2:
private bool mayClose = false;
public void PerformClose()
{
this.mayClose = true;
this.Close();
}
private void Form2_FormClosing(object sender, FormClosingEventArgs e)
{
if (!this.mayClose)
e.Cancel = true;
}
Create a Timer, provide a Tick event handler, enable it and set its Interval to 500 milliseconds:
Create a label to host your desired text. Let's call it label1.
Within and surrounding your Tick event handler do something like this:
private DateTime appearedAt = DateTime.UtcNow;
private void timer1_Tick(object sender, EventArgs e)
{
int seconds = (int)(DateTime.UtcNow - this.appearedAt).TotalSeconds;
this.label1.Text = string.Format(#"Ellapsed seconds: {0}", seconds);
}
Make sure your long running process is happening on a background thread, not on the GUI thread.
Say your long running process can be thought of as the execution of a method called MyProcess.
If that is the case, then you need to call that method from a secondary thread.
// PLACE 1: GUI thread right here
Thread thread = new Thread(() =>
{
// PLACE 2: this place will be reached by the secondary thread almost instantly
MyProcess();
// PLACE 3: this place will be reached by the secondary thread
// after the long running process has finished
});
thread.Start();
// PLACE 4: this place will be reached by the GUI thread almost instantly
Show the form right before the long running process starts. This can be done in any of the 2 places (marked in the previous section of code) called PLACE1 or PLACE2. If you do it in PLACE2 you will have to marshal a call back to the GUI thread in order to be able to interact with the WinForms framework safely. Why am I bringing this up ? It's because maybe the long running process is not started from within the GUI thread at all and you absolutely need to do this.
Close the form right after the long running process finishes. This can be done only in PLACE3 and you absolutely need to marshal a call.
To wrap the earlier 2 bullets and the answer, you could do this:
private void DoIt()
{
Form2 form2 = new Form2();
Action showIt = () => form2.Show();
Action closeIt = () => form2.PerformClose();
// PLACE 1: GUI thread right here
Thread thread = new Thread(() =>
{
form2.BeginInvoke(showIt);
// PLACE 2: this place will be reached by the secondary thread almost instantly
MyProcess();
form2.BeginInvoke(closeIt);
// PLACE 3: this place will be reached by the secondary thread
// after the long running process has finished
});
thread.Start();
// PLACE 4: this place will be reached by the GUI thread almost instantly
}
Finally I've managed to resolve this in the most simplistic manner. And it works like a charm. Here's how to do it:
//Create an instance of the popup window
frmProgressTimer ofrmProgressTimer = new frmProgressTimer();
Thread thread = new Thread(() =>
{
ofrmProgressTimer.startTimer();
ofrmProgressTimer.ShowDialog();
});
thread.Start();
//Perform long running process
ofrmProgressTimer.Invoke((Action)(() =>
{
ofrmProgressTimer.stopTimer();
ofrmProgressTimer.Close();
}));
You can see the code for the popup window in the original post/question, with the only difference that the tick function changes the label text as:
labelText.Text = string.Format("Elapsed Time: {0} seconds.", counter.ToString());
Thank you to everybody for trying to help me out.

Problems with Backgroundworker, GUI is hanged

I am using a background worker below an a form, and when i click a button it should generate a document, but the GUI hangs , i don't know why it does that, because i feel i m using the backgroundworker right.. can anybody helps in this ?
private void btn_GenerateRevDoc_Click(object sender, EventArgs e)
{
DOC_GenerateVersDocBackgroundWorker = new BackgroundWorker();
DOC_GenerateVersDocBackgroundWorker.WorkerReportsProgress = true;
DOC_GenerateVersDocBackgroundWorker.WorkerSupportsCancellation = true;
DOC_GenerateVersDocBackgroundWorker.DoWork += new DoWorkEventHandler(DOC_GenerateVersDocBackgroundWorker_DoWork);
DOC_GenerateVersDocBackgroundWorker.ProgressChanged += new ProgressChangedEventHandler(DOC_GenerateVersDocBackgroundWorker_ProgressChanged);
DOC_GenerateVersDocBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(DOC_GenerateVersDocBackgroundWorker_RunWorkerCompleted);
System.Threading.Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
if (Db.docVersionHistory != null && Db.docVersionHistory.Count > 0)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Title = "Export Review To";
sfd.Filter = "Word files (*.doc)|*.doc|All files (*.*)|*.*";
sfd.FilterIndex = 1;
sfd.FileName = "";
if (sfd.ShowDialog() == DialogResult.OK)
{
if (!DOC_GenerateVersDocBackgroundWorker.IsBusy)
DOC_GenerateVersDocBackgroundWorker.RunWorkerAsync(sfd.FileName);
}
}
else
{
MessageBox.Show("No Review Records were found!");
}
}
void DOC_GenerateVersDocBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
if (this.InvokeRequired)
{
Invoke(new MethodInvoker(delegate
{
DocumentsNavigator.GenerateWordRevisionHistoryDoc(DOC_GenerateVersDocBackgroundWorker, versionsList, Db, (string)(e.Argument));
}));
}
else
{
DocumentsNavigator.GenerateWordRevisionHistoryDoc(DOC_GenerateVersDocBackgroundWorker, versionsList, Db, (string)(e.Argument));
}
}
You don't quite grasp how the BackgroundWorker is supposed to be used, and what the Invoke method does.
The Invoke method causes the code be called on the UI thread. So, don't pass the DocumentsNavigator.GenerateWordRevisionHistoryDoc method through Invoke. RunWorkerAsync is fine. I don't know what types versionsList and Db are, but if they are UI objects, you may need to copy the values you need to a new variable. For example, if versionsList is a ListBox, you should copy the selected values to a new string[], and use that string[] as a parameter to your method.
Here's what you think you wanted to do:
Create new Background worker
initialize your background worker
disable the btn_GenerateRevDoc button
Show the SaveFileDialog
Start the BackgroundWorker (RunWorkerAsync)
On the ProgressChanged event, if you're displaying a progress bar or
something, you can update that, this time you do have to passe it
through the Invoke method.
And on the RunWorkerCompleted event, show a messagebox or something,
and enable the btn_GenerateRevDoc button again
Oh, and this line should absolutely be removed:
System.Threading.Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
Inside your background worker you are again forwarding all the work to UI thread that's why your UI is hanging
if (this.InvokeRequired)
{
//this executes the work on UI thread
Invoke(new MethodInvoker(delegate
{
DocumentsNavigator.GenerateWordRevisionHistoryDoc(DOC_GenerateVersDocBackgroundWorker, versionsList, Db, (string)(e.Argument));
}));
}
else
{
//it will also be executed on UI thread
DocumentsNavigator.GenerateWordRevisionHistoryDoc(DOC_GenerateVersDocBackgroundWorker, versionsList, Db, (string)(e.Argument));
}
}
when i click a button it should generate a document, but the GUI hangs
from your code I can see that you should enter a file name and click ok. Is there a save dialog being opened somewhere?
Try writing the same code without using the background worker. Does it still hang? Also, observed that the condition if (!DOC_GenerateVersDocBackgroundWorker.IsBusy) doesn't make sense as you are creating a new backgroundworker everytime button is clicked
The problem is that You are running the code that hangs the GUI in a worker thread but you do it in an Invoke method.
The Invoke method runs the code in the thread of the GUI and so it hangs.
If you absolutely must call DocumentsNavigator.GenerateWordRevisionHistoryDoc in the GUI thread I don't see how you can make this call without hanging the GUI.
Try to rethink your code so you don't have to run any code in the BackgroungWorker in an Invoke method.
You are doing something unsafe.
instead of Invoke try
if (this.InvokeRequired)
{ this.BeginInvoke(new MethodInvoker(delegate
{

disabling a window

In my application I have a Button. If the button is clicked as select against a database is executed and the result is shown in a ListView.
As the select is quite complex, it takes some time to retrieve the data.
When I click the Button, the Application-Window should be disabled until the data is loaded.
But when I set the IsEnabled-Property of the Window to false, the window gets disabled after the data is loaded.
I tried to disable the Window in an other thread with a BackgroundWorker. But then I get an exception that the window is already in use by another thread.
How can I disable the Window bevore it retrieves the data?
You did the wrong thing in a background thread. You have to affect the UI from the UI thread, and your data loading should occur in a background thread.
The simplest approach is to use a BackgroundWorker to load your data, store that data in a class-level variable, and when your background work is complete, the UI re-enables and loads the data from the class-level variable.
I'd think you'd move the database activity to the background thread to leave your UI responsive (even if it's only to disable it) rather than the other way around.
try this:
BackgroundWorkerHelper.DoWork<Type of object you want to retrieve>(
() =>
{
//Load your data here
//Like
using (MarketingService svc = new MarketingService())
{
return svc.GetEmployeeLookupTable();
}
},
(args) =>
{
this.IsEnable = true;
if (args.Error == null)
{
Your Target Datasource= args.Result;
}
});
this.IsEnable = false;
I will suggest "BusyDialog" window in addition to background thread.
Yous busy dialog can be an animation displaying it is doing something, and modally blocking any user input as well.
public partial class BusyDialog : Window
{
public BusyDialog()
{
InitializeComponent();
}
public static T Execute<T>(DependencyObject parent, Func<T> action)
{
Window parentWindow = null;
if (parent is Window)
{
parentWindow = parent as Window;
}
else
{
parentWindow = Window.GetWindow(parent);
}
T val = default(T);
Exception le = null;
BusyDialog bd = new BusyDialog();
bd.Owner = parentWindow;
ThreadPool.QueueUserWorkItem((o) =>
{
try
{
val = action();
}
catch (Exception ex)
{
le = ex;
}
bd.EndDialog();
});
bd.ShowDialog();
if (le != null)
{
Trace.WriteLine(le.ToString());
throw new Exception("Execute Exception", le);
}
return val;
}
private void EndDialog()
{
Dispatcher.Invoke((Action)delegate() {
this.DialogResult = true;
});
}
}
Now you can use following way to call your method asynchronously,
List<Result> results = BusyDialog.Execute( this ,
()=>{
return MyLongDatabaseLoadingCall();
});
This is what happens,
BusyDialog is displayed modally, blocking any user input as well as displaying busy animation
A call to your method MyLongDatabaseLoadingCall is executed in ThreadPool.QueueUserItem, which asynchronously calls your method in different thread (Same as background threading functionality suggested by others here).
Animation continues till the call is executing
When your method ends, BusyDialog is ended and everything is back to how it was.

Categories

Resources