C# Winforms: selective disabling of UI whilst a thread runs - c#

I have a quite complex form that presents an option to run a script (our own type). Whilst it runs, I don't want to lock the UI completely, so I would like to start it in a thread. So far so good, but to prevent the user messing with things I need to selectively disable parts of the UI. I could recursively set Enabled = false and then Enabled = true when the thread ends. But this ignores the state of the control at the time of running (ie controls which were disabled for various reasons would be incorrectly re-enabled). Short of building a tree of booleans, is there some other way to block input (such as the GlassPane type in Java)?

Don't use DoEvents, it's evil.
Use a panel and add all the controls you want to disable in it. When the panel will be disabled, all inner controls will appear to be disabled but the value of their Enabled property won't be actually modified.
Here's a working example:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// Disables UI elements using the panel
this.SetPanelEnabledProperty(false);
// Starts the background work
System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(this.Worker));
}
private void Worker(object state)
{
// Simulates some work
System.Threading.Thread.Sleep(2000);
// Now the work is done, enable the panel
this.SetPanelEnabledProperty(true);
}
private void SetPanelEnabledProperty(bool isEnabled)
{
// InvokeRequired is used to manage the case the UI is modified
// from another thread that the UI thread
if (this.panel1.InvokeRequired)
{
this.panel1.Invoke(new MethodInvoker(() => this.SetPanelEnabledProperty(isEnabled)));
}
else
{
this.panel1.Enabled = isEnabled;
}
}
}

Could you use a panel populated with the controls you want disabling while the script runs, then re-enable the panel when the script has ended.
Alternatively you could start a Process for the script.

You can solve this either using the Application.DoEvents() method, or you have to write a delegate which invokes the corosponding control.
I think the Application.DoEvents() would be the simplest way.
You should call Application.DoEvents() in the loop on your thread.
For the delegate version, you find here some information: http://msdn.microsoft.com/de-de/library/zyzhdc6b.aspx

Related

How to make a form stay above another

In my .NET application I am showing a form with a "Please wait" message if some task takes a while to tell the user that the program still works. This form has to run in it's own thread since the main thread is the one doing work and therefore busy. I read that one can use Form.Owner for this task, but I don't think that works if the forms run on different threads.
The problem is that the wait form can be hidden behind the main form in which case the user couldn't see it and can't bring it to the front, since it has no task bar button (an it musn't have one).
My question is if it is possible to let the wait form stay above the main form without making it an AlwaysOnTop form (which would stay above ALL windows)?
Your main thread should not be doing work. It should be handling UI and nothing else.
The right way to do this is to perform any and all time-consuming work in an asynchronous manner (e.g. in a separate thread), and keeping all of your user interface in the main thread. Then you can simply show the "Please wait" message form by calling the Form.ShowDialog() method. This will force focus to that dialog and keep it on top of its parent form (don't forget to pass the parent form reference to the ShowDialog() method).
Without a code example, I can't say exactly how this would look in your specific scenario. But the general idea looks something like this:
private void button1_Click(object sender, EventArgs e)
{
using (Form form = MyWaitMessageForm())
{
form.Shown += async (sender1, e1) =>
{
await Task.Run(() => MyLongRunningWork());
form.Close();
}
form.ShowDialog(this);
}
}
You can use the Form.TopMost property for this.
You can also use the following code:
protected void SetZOrder(IntPtr bottom, IntPtr top) {
const int flags = 0x0002 | 0x0001;
NativeMethods.SetWindowPos(bottom, top, 0, 0, 0, 0, flags);
}
bottom - the pointer of the main form, top - the pointer of the wait form. To get a pointer, use Form.Handle property. And call the SetZOrder via the BeginInvoke method of the parent form.
You could use the Form.ShowDialog(IWin32Window)
Form1 testDialog = new Form1();
testDialog.ShowDialog(this)

Form And Code Unsynched

I have a textbox with some text in it ("hello"). If I change the text to anything else and get the text from the textbox (through the code), I'll get "hello" although I changed it.
In another case, when I change a checkbox's check state, the checkbox won't visually tick (or untick).
Anyone has an idea what's going on and how to sync them?
I've opened a new project,
thats the only function on my form :
public void Switch()
{
Checkbox1.Checked = !Checkbox1.Checked;
}
and I call it from the program.cs :
static MainForm MyForm;
MyForm = new MainForm();
MyForm.Switch();
I assume that you are simply instantiating MainForm in Main and then calling Switch.
Which means that you are not sending the request on the UI Thread.
So, you can Invoke your code to run on the UI thread:
public void Switch()
{
// true if off of the UI thread, and thus must be Invoked
if (this.InvokeRequired)
{
// off UI thread; put it onto it
this.Invoke(Switch);
}
else
{
// on UI thread
Checkbox1.Checked = ! Checkbox1.Checked;
}
}
You should only interact with the UI from the UI Thread. You should not litter your code with Invokes unless you expect to call it off of the UI Thread, or you provide a means for others to do so (some public API).

How would I stop a thread, allow a UI event to be handled, and then "restart" the thread?

I have a form that appears as shown in the attached image. I have two parts of the form that concern this question: the TabControl, and the Panel, as shown in the image. It should be noted that the panel is NOT within the TabControl.
My situation is that I have a thread that executes continuously when the button, displayed in melt-your-eyes green in the Panel, is clicked. The thread polls the device which I'm interfacing with and updates the controls in the "Status" GroupBox at the bottom of the TabControl. When the user clicks on a control in the TabControl (tabControl_Enter event), I trigger a ManualResetEvent which lets the thread finish its iteration so that I can perform the IO required by the clicked control. The code to to suspend the thread is as follows:
private void StopSynchThread()
{
synchWaitHandle.Reset();
//various UI changes
}
private void updateSynchStat()
{
while (true)
{
synchWaitHandle.WaitOne();
try
{
updateSynch();
}
}
What I would like to do is then restart the thread automatically, instead of by button press, as is currently done. What I'm trying to do is avoid having to restart the thread by conditionally calling StartSynchThread() within each of the "bazillion" UI event handlers. StartSynchThread() is defined as:
private void StartSynchThread()
{
synchWaitHandle.Set();
}
Is there a precedent or decent paradigm for handling this? Without any concept of how to do so, I was thinking that I could alter my function that performs the IO with the device to generate an event after it gets a response from the device, but that seems inelegant.
Any ideas? I appreciate your insights. Thanks.
If you really can fire it off with a simple button click, you ought to be able to just put a timer on the form that will periodically check for the right conditions and then "push" the button (call synchWaitHandle.Set();) automatically.

Weird Winforms Bug?

I'm making a Calendar application for myself to use, and to learn.
I've had no trouble until now with mutliple forms, and opening new ones on top of each other, etc.
Here's an example:
private void button1_Click(object sender, EventArgs e)
{
if (ceForm != null) ceForm.Close();
ceForm = new CalendarEventForm();
ceForm.Show();
}
Anyway, I now started to add timers to pop up a 'reminder' form before important events on my calendar will occur (i.e. 1 hour before etc.).
The code sets up the timers when the program is loaded, and then when each timer elapses, this is called:
static void lazyTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
mainForm.ShowReminder((sender as LazyTimer).ReferredEvent);
}
LazyTimer is exactly the same as a System.Timers.Timer except the added propery 'ReferredEvent', which refers to the calendar event that is to be reminded of.
MainForm.ShowReminder() is as follows:
public void ShowReminder(LazyEvent lazyEvent)
{
ReminderForm newReminder = new ReminderForm();
newReminder.LoadEvent(lazyEvent);
newReminder.Show();
}
The weird thing is that ReminderForm crashes. I've tried it with other forms (such as CalendarEventForm, which I know works normally) and they crash too. However, when I try to load the ReminderForm by pressing a button on my main form, it works fine.
Why do my forms crash when loaded (indirectly) by a timer?
Short answer: Use a System.Windows.Forms.Timer, not a System.Timers.Timer.
The reason is that the System.Timer.Timers class will fire the timer event on another thread, and you are not able to directly do UI operations from another thread than the main UI thread.
If it's wrapping a System.Timers.Timer, it will be firing on a thread-pool thread, which means you can't do UI operations there.
Use a System.Windows.Forms.Timer instead, or set the SynchronizingObject in the System.Timers.Timer to a UI object, so that the timer will fire on the UI thread.
EDIT: Another point... personally I'd probably use a lambda expression or anonymous method as the timer's Tick event handler, capturing the relevant event that way and thus avoiding the extra class and the extra method:
// Presumably we've got a local variable here, e.g. currentEvent
timer.Tick += delegate { mainForm.ShowReminder(currentEvent; };
You are running into a threading issue.
Please use System.Windows.Forms.Timer when working with System.Windows.Forms.
The System.Timers.Timer does not invoke the event on the applications event loop, but calls the event handler directly, which leads, in your case, to a cross thread operation which is not supported by Forms, and your application crashes.
In contrast, System.Windows.Forms.Timer will fit seamlessly into the component model of System.Windows.Forms.

Starting multiple gui (WinForms) threads so they can work separetly from one main gui in C#?

I've one MainForm window and from that user can press 3 buttons. Each of the button starts new Form in which user can do anything he likes (like time consuming database calls etc). So i decided to put each of the forms in it's own threads:
private Thread subThreadForRaportyKlienta;
private Thread subThreadForGeneratorPrzelewow;
private Thread subThreadForRaporty;
private void pokazOplatyGlobalne() {
ZarzadzajOplatamiGlobalneDzp varGui = new ZarzadzajOplatamiGlobalneDzp();
varGui.ShowDialog();
}
private void pokazRaportyKlienta() {
RaportyDzpKlient varGui = new RaportyDzpKlient();
varGui.ShowDialog();
}
private void pokazRaportyWewnetrzne() {
RaportyDzp varGui = new RaportyDzp();
varGui.ShowDialog();
}
private void pokazGeneratorPrzelewow() {
ZarzadzajPrzelewamiDzp varGui = new ZarzadzajPrzelewamiDzp();
varGui.ShowDialog();
}
private void toolStripMenuGeneratorPrzelewow_Click(object sender, EventArgs e) {
if (subThreadForGeneratorPrzelewow == null || subThreadForGeneratorPrzelewow.IsAlive == false) {
subThreadForGeneratorPrzelewow = new Thread(pokazGeneratorPrzelewow);
subThreadForGeneratorPrzelewow.Start();
} else {
}
}
private void toolStripMenuGeneratorRaportow_Click(object sender, EventArgs e) {
if (subThreadForRaporty == null || subThreadForRaporty.IsAlive == false) {
subThreadForRaporty = new Thread(pokazRaportyWewnetrzne);
subThreadForRaporty.Start();
} else {
}
}
private void toolStripMenuGeneratorRaportowDlaKlienta_Click(object sender, EventArgs e)
{
if (subThreadForRaportyKlienta == null || subThreadForRaportyKlienta.IsAlive == false) {
subThreadForRaportyKlienta = new Thread(pokazRaportyKlienta);
subThreadForRaportyKlienta.Start();
} else {
}
}
I've got couple of questions and i hope someone could explain them:
When i use Show() instead of ShowDialog() the windows just blink for a second and never shows. What's the actual difference between those two and why it happens?
When i use ShowDialog everything seems normal but i noticed not everything gets filled properly in one of the gui's (one listView stays blank even thou there are 3 simple add items in Form_Load(). I noticed this only in one GUI even thou on first sight everything works fine in two other gui's and i can execute multiple tasks inside those Forms updating those forms in background too (from inside the forms methods). Why would this one be diffrent?
What would be proper way of doing this? Tasks performed in each of those Forms can be time consuming and i would like to give user possibility to jump between those 4 windows without problem so he can execute what he likes.
The difference is with Modal and Modeless Windows Forms.
Modeless forms let you shift the
focus between the form and another
form without having to close the
initial form
Show() method is used for this purpose
Show() shows the new form, then returns. If this is all the thread is doing than the thread will exit, and that will destroy the form.
ShowDialog() shows the form, and then begins running a message pump, until the form is hidden or destroyed, ShowDialog() doesn't return, so your thread keeps running.
If You mean for these forms to each behave like a separate application window. then you could also use Application.Run() after form.Show() to run a message pump for the form on that thread. The drawback to doing it this way, is that when any one of your forms is closed, it might end up taking down the whole process because of the way WM_QUIT is handled.
But other than the way you would deal with closing down your application, Form.ShowDialog() is very much like Form.Show() followed by Application.Run(). The conditions that cause the message pump to exit are a bit different between these to, so you would choose one or the other mostly based on how you want to your application to handle closing one of your forms.
You should put the time-consuming tasks in their own BackgroundWorker threads. Keep all of the forms in the main thread.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
1: Show() is not blocking, that is it shows the window and then returns. Afterwards, the varGui variable goes out of scope and is finalized by the garbage collector => disappears.
2: You'd need to show the updating code to get a definite answer, but as you can only update the contents of a form when running in the thread that created the form. Doing it otherwise is unreliable, so is the usual culprit when form updates don't work. In a method of a form class, call:
if (InvokeRequired) {
.Invoke(..); // call back this same method on the right thread
} else {
// dowork
}
to manage the UI.
3: The proper way is what Jake wrote: use one thread for all GUI stuff and BackgroundWorker threads for, well, background work.

Categories