Happy Friday SO!
I'm building a multi-WinForm application and am having some troubles.
I have a main WinForm that stays open at all times. It hits a database every minute looking for changes, and if there is a change, it will open the second WinForm (this may seem like a stupid way to do things, but for my purpose, this is the best method).
Just sticking the following code into my Form1.cs doesn't do the trick:
Application.Run(new Form2());
Can you guys point me in the right direction? I have no idea where to turn.
Form2 form2 = new Form2();
form2.Show();
and to prevent a ton of forms being opened, maybe:
Form2 form2 = new Form2();
form2.ShowDialog();
#Comment:
A BackgroundWorker is used to keep your current UI Thread responsive. It was not designed to keep multiple forms pumping happily along. Look into running your intensive code as a Background thread within a ThreadPool.
If what you wish is to launch a long process and to show the progress to the user, for example just like when you have a progress bar or something alike, you should use a BackgroundWorker to do the job. Here's a simple example:
public partial class ProgressForm : Form {
// Assuming you have put all required controls on design...
// Allowing some properties to be exposed for progress update...
public properties MaximumProgress {
set {
progressBar1.Maximum = value;
}
public properties OverallProgress {
set {
progressBar1.Value = value;
}
}
public partial class MainForm : Form {
private BackgroundWorker backgroundWorker1;
private ProgressForm _pf;
public MainForm() {
InitializeComponent();
backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
// Assuming process starts on Button click.
private void button1_Click(object sender, EventArgs e) {
_pf = new ProgressForm();
_pf.MaximumProgress = number-of-elements-to-treat-returned-by-prevision-or-whatever-else;
// Launching the background workder thread.
backgroundWorker1.RunWorkerAsync(); // Triggering the DoWork event.
// Then showing the progress form.
_pf.ShowDialog();
}
private void backgroundWorker1_DoWork(object sender, EventArgs e) {
LaunchProcess();
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
_pf.OverallProgress = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, EventArgs e) {
_pf.Close();
_pf.Dispose();
}
private void LaunchProcess() {
// Do some work here...
// Reporting progress somewhere within the processed task
backgroundWorker1.ReportProgress();
}
}
This is not a compileable code as its purpose is to illustrate the main idea.
Now, is this something alike you want to do?
Related
The software starts thread with some calculations and then shows another Form as a waiting dialog with ShowDialog. When the BackgroundWorker thread finishes its work, the Form is closed in the RunWorkerCompleted event and another calculation is start with another Form as a waiting dialog (with ShowDialog again).
The problem is that the first waiting dialog is still visible until the second waiting dialog is closed. How to wait with showing the second dialog after the first dialog is really closed?
Simple code to reproduce:
private BackgroundWorker _bgw = new BackgroundWorker();
private Form2 _msg = new Form2();
private Form3 _msg2 = new Form3();
public Form1()
{
_bgw.DoWork += BgwDoWork;
_bgw.RunWorkerCompleted += BgwRunWorkerCompleted;
_bgw.RunWorkerAsync();
_msg.ShowDialog();
}
private void BgwDoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(5000);
}
private void BgwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_msg.Close();
_msg2.ShowDialog();
}
Note that ShowDialog() is a blocking call. You still have not returned from the constructor when you show _msg2.
This is a quick fix:
public Form1()
{
_bgw.DoWork += BgwDoWork;
_bgw.RunWorkerCompleted += BgwRunWorkerCompleted;
_bgw.RunWorkerAsync();
_msg.ShowDialog();
_msg2.ShowDialog(); // here
}
private void BgwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_msg.Close();
//_msg2.ShowDialog(); // not here
}
UPDATE:
As mentioned in the comments section the problem was solved, but I do not understand why my way of implementation was wrong.
I have this situation:
I have a device which can be triggered with an event in my WPF project.
This event pulls Data from my device at a polling rate of 1ms. I want to process
the data in different threads.
My approach was, to start a backgroundworker which registers the device event (I read events run on the thread they are called from). In the device event itself the data is saved to an object, which is declared in my form. After that the labels in the WPF form a are refreshed with a Invoke Method.
This happens until someone presses cancel on a button in my form, which unregisters the device event and stops the thread.
Here is some code I use:
Declaration in my Main Window:
public partial class MainWindow : Window
{
private BackgroundWorker worker = new BackgroundWorker();
private MeasureObject mObject = new MeasureObject();
... }
This is my initialization:
public MainWindow()
{
InitializeComponent();
this.worker.WorkerSupportsCancellation = true;
this.worker.DoWork += worker_DoWork;
this.worker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
If this button is press i run my background worker:
private void btnStartMeasure_Click(object sender, RoutedEventArgs e)
{
this.worker.RunWorkerAsync();
}
Here I register my event for the device. It should now run on my worker thread. I tried to declare the event itself here too, but it did not work, so I placed it in my main windows.
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.myController.ControlCenter.Diagnostics.NewDiagPacketArrived += new EventHandler<NewDiagPacketArrivedEventArgs>(Diagnostics_NewDiagPacketArrived);
// run all background tasks here
}
This is not needed and empty. The worker is only cancelled if the user sets it on cancel.
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
}
This event is triggered in my Window and calls two functions, it should run on the backgroundworker if I am correct.
private void Diagnostics_NewDiagPacketArrived(object sender, NewDiagPacketArrivedEventArgs e)
{
try
{
Measure(e);
this.Dispatcher.BeginInvoke( new Action(() => { SetStates(e); }),System.Windows.Threading.DispatcherPriority.Input);
}
catch
{
}
}
Measure gets the e Object from the device and saves it to a Dataobject i created
private void Measure(NewDiagPacketArrivedEventArgs e)
{
lock(this.mObject)
{
this.mObject.ID = this.list.Count;
....
this.list.Add(this.mObject);
}
}
SetStates refreshed the GUI
private void SetStates(NewDiagPacketArrivedEventArgs e)
{
lock(this.mObject)
{
this.lblID.Content = this.mObject.ID;
}
}
The problem with my code is if I cancel the event and the thread with this code:
private void btnStopMeasure_Click(object sender, RoutedEventArgs e)
{
this.myController.ControlCenter.Diagnostics.NewDiagPacketArrived -= Diagnostics_NewDiagPacketArrived;
this.worker.CancelAsync();
}
And try to get the list where I added my objects, all objects have the same ID's and values. It seems like as soon as I unregister the event or press the stop measure button, all mObjects in my list get overwritten with the mObject at the time when I unregister the event.
so my list looks like this:
list[0].ID = 1
list[1].ID = 1
list[2].ID = 1
rather than this:
list[0].ID = 1
list[1].ID = 2
list[2].ID = 3
Maybe you can help?
Your problem is that you are not creating a new instance of the mObject - you only create one of them here:
private MeasureObject mObject = new MeasureObject();
Your code then adds the SAME object to the list, and updates that. You need to make a new object each time and put it in the list.
this.mObject.ID = this.list.Count;
....
this.list.Add(this.mObject);
Currently your list is a list of the same object.
I am trying to make a simple application in WPF which will open a new window in a thread it's behaving oddly.
ArrayList formArray = new ArrayList();
Thread th;
Window1 vd;
public void Start()
{
vd = new Window1();
formArray.Add(vd);
vd.ShowDialog();
}
public void StartCall()
{
th = new Thread(new ThreadStart(Start));
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
StartCall();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((Window1)(formArray[0])).Show();
}
Window1 code is
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
When trying to open it again, it just throws an error The calling thread cannot access this object because a different thread owns it.
When trying to use dispatcher.. invoke... all these things didn't help.
To make it even weirder, this same code worked in a Windows Forms application.
Maybe it's related to this line? th.SetApartmentState(ApartmentState.STA);?
It might be this guys, but if I won't add it, it will also fail with an error that
Additional information: The calling thread must be STA, because many UI components require this.
Edit:
Added the force run on dispatcher on your thread.
I also added a Display method to show the dialog depending on the dispatcher who is calling. Hope that help !
Also, as explained here: Dispatcher.Run
You should shutdown the dispatcher of the corresponding thread when you are done.
MainWindow:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
StartCall();
}
ArrayList formArray = new ArrayList();
Window1 vd;
Thread th;
public void Start()
{
vd = new Window1();
formArray.Add(vd);
vd.ShowDialog();
System.Windows.Threading.Dispatcher.Run(); //ok this is magic
}
public void StartCall()
{
th = new Thread(new ThreadStart(Start));
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
StartCall();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((Window1)(formArray[0])).Display();
}
Window1:
void Window1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
public void Display()
{
if (!Dispatcher.CheckAccess())
{
Dispatcher.BeginInvoke((Action)Display);
return;
}
this.Show();
}
You can't call .Show on your window from a thread other than the one it was created on (that's basically what the error message is telling you!). Fortunately, as you suggested, this is what the dispatcher is for: to marshal calls onto the correct thread. But you have to use the correct dispatcher for the job!
Each control in WPF (including a Window) has a .Dispatcher property that gets the Dispatcher for that control's thread. My guess is that you were using the one from your main window when trying to re-open the dialog - which is the wrong one. Instead, if you use this in your Button_Click you will have more luck:
var window = (Window1)formArray[0];
window.Dispatcher.Invoke(window.Show); // note: use the dispatcher that belongs to the window you're calling
(NOTE: this isn't to say that this is a typically useful/recommended design pattern. In fact, it's often going to cause more problems than it solves. But, it's certainly something you can choose to do.)
can someone please let me know why the System.Windows.Forms.Timer continues to show multiple message boxes? I thought that it is on GUI thread ... and therefore after the first messagebox the GUI thread should block. But this is not the case
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
int nValue = 0;
void tmr_Tick(object sender, EventArgs e)
{
nValue++;
MessageBox.Show(nValue.ToString());
}
System.Windows.Forms.Timer tmr = new System.Windows.Forms.Timer();
private void btnStartTimer_Click(object sender, EventArgs e)
{
tmr.Interval = 500;
tmr.Enabled = true;
tmr.Tick += new EventHandler(tmr_Tick);
}
}
The MessageBox.Show() method includes (as all modal dialogs do) a message loop that continues to pump window messages.
Window messages are what allow a window to interact with the user (update itself, accept input, etc.), as well as what allows the Forms.Timer class to work.
If you want your Forms.Timer to stop ticking when the dialog is shown, you need to set the timer's Enabled property to false before you show the dialog.
In your Tick event stop the timer and then start again after MessageBox.Show like:
void tmr_Tick(object sender, EventArgs e)
{
tmr.Enabled = false;
nValue++;
MessageBox.Show(nValue.ToString());
tmr.Enabled = true;
}
The reason you are getting repeated MessgeBoxes is because your timer is continuing after showing the first MessageBox.
A message box does not block the GUI-Thread. It's as simple as that. You can interact with the message box, after all :)
Also: The internal workings of the timer are not clear, but I would guess that it runs on another thread and just returns on the GUI-Thread.
I have a MainWindow with eventhandler which is not working properly. I have made simple model of this problem. Please see comment in code where the problem is:
public partial class MainWindow : Window
{
public event EventHandler Event1;
public MainWindow()
{
Event1 += MainWindow_Event1;
InitializeComponent();
}
void MainWindow_Event1(object sender, EventArgs e)
{
textBox1.Text = "wth!?"; //Not changing text box. Not showing message. If delete this line, it will work fine
MessageBox.Show("raised");
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
EventHandler evt = Event1;
while (true)
{
Thread.Sleep(500);
evt(null, null);
}
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerAsync();
}
}
Please explain this behavior and how can I fix it?
The problem is that you're invoking the event from a background thread. This will not work and the program is simply hanging when trying to access the TextBox. However, if you change this code:
textBox1.Text = "wth!?"; //Not changing text box. Not showing message. If delete this line, it will work fine
MessageBox.Show("raised");
to this:
this.Dispatcher.BeginInvoke((Action)delegate()
{
textBox1.Text = "wth!?"; //Not changing text box. Not showing message. If delete this line, it will work fine
MessageBox.Show("raised");
});
it'll work for you.
You can't update the UI elements from the background thread.
The worker thread fails by exception trying to access the UI element (Text property). So messageBox isn't showing as well. Use notification mechanisms, or Dispatcher calls (there is a wast amount of information like this on the web)
Here are possible duplicates/help:
Update GUI using BackgroundWorker
Update GUI from background worker or event
This problem is because you need to use the Synchronization Context of the current Thread for comunicating between threads, some thing like this
private void Button_Click(object sender, RoutedEventArgs e)
{
var sync = SynchronizationContext.Current;
BackgroundWorker w = new BackgroundWorker();
w.DoWork+=(_, __)=>
{
//Do some delayed thing, that doesn't update the view
sync.Post(p => { /*Do things that update the view*/}, null);
};
w.RunWorkerAsync();
}
Please check this question, hope can helps...