Displaying a MessageBox over a DialogForm causing a weird UI glitch - c#

I've got a button on my form, and when it's clicked it does the following:
private void btnCheckSVN_Click(object sender, EventArgs e)
{
wait = new DevExpress.Utils.WaitDialogForm("Fetching File SVN Status",
"Please Wait");
wait.AutoSize = true;
wait.Visible = false;
bgwSVN.RunWorkerAsync();
wait.ShowDialog();
}
private void bgwSVN_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = svn.SvnStatusEventArgsToDataTable(svn.CheckSVN(_localPath));
}
private void bgwSVn_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DataTable dt = (DataTable)e.Result;
dt.DefaultView.Sort = "File ASC";
gdcSVN.DataSource = dt;
gdcSVNDefaultView.BestFitColumns();
xtcTabs.SelectedTabPageIndex = 1;
lblTotalFileCount.Text = dt.Rows.Count.ToString();
if (dt.Rows.Count == 0)
XtraMessageBox.Show("No files found.",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
wait.Close();
}
With this order of operations, you can (obviously) still see the Wait Dialog behind the MessageBox and it just looks really sloppy in my opinion.
If I switch the IF statement and the Close() call, my WaitDialogForm half-disappears for lack of a better term. There's an empty rectangle with a red frame and a giant X going through it, then the MessageBox on top of that as its own form.
This is my first time messing around with any sort of multithreading or using the BackgroundWorker objects at all, so I'm sure I'm just missing something really stupid but I don't know what?
I tried completely removing the MessageBox from this event, and instead doing this:
wait.Close();
lblTotalFileCount.Text = dt.Rows.Count.ToString();
Then handling the label's TextChanged event and checking to see if it should display the MessageBox then... but I still get the same results with the red box.
Edit; Also, a weird thing I noticed. On form load, lblTotalFileCount was set to 0. When this method runs and say it found 0 files, it would re-set lblTotalFileCount.Text to zero... Which did not trigger the TextChanged event? I understand that it was being set to the same thing it already is, but it was still technically changed. Am I missing something, or does the event actually check for that condition and "skip" it if that's the case? I had to just set the label to be an empty string instead... But say they run the application, it gets set to 0, alerts them, they make some changes, run it again... still 0, it won't alert them.
Edit 2; I thought maybe if I tried calling wait.Close() in the TextChanged event for the label as mentioned in my first edit before the MessageBox shows up, maybe it'd work? Nope. Same Problem. And then if I click the button a second time, the form crashes because wait has been disposed? It's a form level property why would it get disposed?
Edit 3; Well, if I call wait.Dispose in the TextChanged event, I get the desired behavior. Except it minimizes the main application and just shows me the MessageBox unless there's nothing behind it. Son of a...

Make Thread-Safe Calls to Windows Forms Controls. To modify a winform or a control from another thread (BackgroundWorker does that) you should make this call from the main thread. In this question you have your answer: How to update the GUI from another thread in C#?

Related

Calling a KeyDown Event on Form Load

I would want to launch a KeyDown Event on Form_Load however its taking me somewhere else in the Form_Load event.
Form_Load:
int static_int = 0;
private void Form1_Load(object sender, EventArgs e)
{
if(condition == true)
{
txtInput.Text = "something";
txtInput.Focus();
SendKeys.Send("{Enter}");
int somegeneratednubmer = 20;
static_int = static_int + somegeneratednumber;
//somemore code here
}
}
KeyDown:
private void txtInput_KeyDown(object sender, KeyEventArgs e)
{
if(e.KeyCode == Keys.Enter)
{
static_int = 10;
//somemore codes here too
}
I would like to get the SUM of static_int and somegeneratednumber which is 30. However, after Debugging, I'm getting its initialized value of 0. From what I understood, after SendKeys.Send("{Enter}") the KeyDown event should proceed.
Why is it not??
How would I get the correct result? I really should do the KeyDown event on Form_Load, a conditional event...
or What am I doing wrong here?
Note: originally static_int is initialized on a Class
No, the KeyDown even will proceed at the earliest possible moment, which is when the appropriate message is executed from the form's message queue. That cannot happen before the Load event finishes, because that also on the message queue. Even if that weren't the case, SendKeys doesn't wait for the action to be processed. It just sends the message and returns immediately.
Another problem is that SendKeys sends the virtual keys to the currently active window. That can never be your window, since your window isn't even shown yet! When something behaves weird, a good first step is to read the documentation.
So, why is the value of static_int zero, instead of 20 or 30? Well, the likeliest case is an unhandled exception, and I'm pretty sure that's exactly what happens when you do tbxInput.Focus. The control doesn't quite exist yet, and it can't be made the input focus. If you have trouble understanding all this, you might want to find some book on the basics of how Windows windows work - there's nothing .NET can do about it, and it's places like this where the (very pretty) .NET abstraction leaks a lot. If you're planning to do any Windows UI development, you really need to know at least the basics.
However, that's completely unnecessary anyway. You don't have to execute a KeyDown event. Just make a method that's called from both the Load event handler and the KeyDown event handler.
try adding this event instead
Form1 isn't loaded yet so no events yet.
private void Form1_Shown(Object sender, EventArgs e)
{
SendKeys.Send("{Enter}");
}
But truly this design is wrong

How do I prevent any button click events from queuing until the event handle is finished with the first call

I want to prevent a button click from queuing. In testing I have a Form, a Button and in the Code-Behind I have the event handler:
private void button1_Click(object sender, EventArgs e)
{
if (_codeRunning)
return;
_codeRunning = true;
//Application.DoEvents();
//button1.Enabled = false;
_click ++;
Debug.WriteLine("Click Number: " + _click);
Task.Delay(5000).Wait();
//button1.Enabled = true;
_codeRunning = false;
}
When I run debug and click the button twice or three or four times rapidly, Debug Output shows each click about five seconds after the last one. What I would like it to show is a single Click and drop the rest until first Event is complete.
I have also tried to disable the button, as well as temporarily remove the Handler from the Button_click event. It is all the same results.
There are various amounts of trouble you'll get into when you hang-up the UI thread like this. This is certainly one of them, nothing pleasant happens when the user wildly bangs on the button to try to get something noticeable to happen. And sure, those clicks won't get lost, they stay stored in the message queue. To activate your Click event handler again when your event handler stops running.
Pretty important to learn how to use the BackgroundWorker or Task classes to avoid this kind of trouble. Just setting the button's Enabled property is then enough to solve this problem.
Purging the mouse clicks from the message queue is technically possible. But ugly to do, it requires pinvoke. I'll hesitantly post the alternative, don't assume that this is in general a good strategy. You'll need to read this post to have some insight into why DoEvents() is a dangerous method.
private void button1_Click(object sender, EventArgs e) {
button1.Enabled = false;
button1.Update();
'' long running code
''...
Application.DoEvents();
if (!button1.IsDisposed) button1.Enabled = true;
}
The Update() call ensures that the user gets the feedback he needs to know that banging the button repeatedly isn't going to do anything useful. The DoEvents() call will dispatch all the queued-up mouse clicks, nothing happens with them since the button is still disabled. The IsDisposed test is essential to solve the problem with DoEvents(), it ensures your program won't crash when the user clicked the window's Close button while the code was running.
Use the HourGlass class in this post to provide more feedback.
I had a button that on click event was going to run a method. Same issue happent and when the user clicked multiple times the method was triggered multiple times. So I made a boolean and changed it value when the method started.
private bool IsTaskRunning = false;
private void MyMethod()
{
if ( IsTaskRunning==false )
{
IsTaskRunning=true;
// My heavy duty code that takes a long time
IsTaskRunning=false; // When method is finished
}
}
So now the method runs only if it's done the last time.

Disabling a Textbox Using TextChanged Event

The form I am using requires a copy pasted URL. I am trying to have a textChanged event that will check the url as soon as it is pasted, telling the user whether it is valid or invalid. I also want to be able to lock out the textbox when this happens, with a message saying something like "Processing...".
The problem is with the code below, the textbox is never disabled, the program will do the checkUrl() method and the textbox is never disabled even though it is first to execute (I assume it is but the fact there is a function call right underneath it is messing around with something or getting higher priority).
How do I go about making the control visually disabled while the method runs?
private void urlTxtBx_TextChanged(object sender, EventArgs e)
{
urlTxtBx.Enabled = false;
checkUrl();
urlTxtBx.Enabled = true;
}
I think this is happening because the Application needs to complete all the active threads before disabling the TextBox. Please try the following code:
private void urlTxtBx_TextChanged(object sender, EventArgs e)
{
urlTxtBx.Enabled = false;
Application.DoEvents();
checkUrl();
urlTxtBx.Enabled = true;
}
This will let the UI to be updated. For more details check here.

How do I "Hide()" a Modal WPF Window without it closing?

I have a WPF window that is run on a background thread as a sort of "notifier window"... when an event is raised, it displays a message... a user clicks the "Snooze" button and I call this.Visibility = Visibility.Collapsed
The very moment that I hide the window (either by calling this.Hide() or setting the Visibility as mentioned above)... the "ShowDialog()" code releases the window and closes it.
This is absolutely a bug in the WPF code (which I've identified via reflector)... but my question remains. Has anyone been able to come up with a work-around for this issue?
I've tried many things and am now reaching out to ya'll smart people :)
You can't hide a modal dialog. That's like asking, "How do I get to 100mph in reverse?" You don't, you drive the car forwards.
Use Show, not ShowDialog. Alternately you can simply re-ShowDialog when it needs to become visible again.
Timothy's Answer is good. I just needed for my scenerio to add the following
window.Closed += new EventHandler(window_Closed);
window.Show();
System.Windows.Threading.Dispatcher.Run();
and then in the event...
void window_Closed(object sender, EventArgs e)
{
System.Windows.Threading.Dispatcher.ExitAllFrames();
}
I needed to do this because it was hanging on the Run after the form was really closed.
In order to show modal window always use ShowDialog().
Use Close() instead of Hide().
Handle the FormClosing event like that:
private void OnFormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
this.Visible = false;
}
OK, and as quickly as that - my boss (old C++ goofy guy that he is) figured out the answer.
Here was the code inside of my background thread (which is set to STA mode):
// Show dialog - keeps the thread open and shows the window! Yay!!!
new BeamUI.Notifier.NotifierWindow().ShowDialog();
And here is the modification, that strangely enough works perfectly :)
// Show... hmm, that shows the window... but how do I keep this thread open?
new BeamUI.Notifier.NotifierWindow().Show();
// ZOMG - a line of code that JUST keeps the thread (and msgpump) going!!!
System.Windows.Threading.Dispatcher.Run();
And that's it.
This kinda thing makes me hate C++ people though, and makes me want to just say "if you just built it right in the first place I wouldn't have to look for a work-around!" (j/k)

Why is Form.Refresh() not working?

I'm running a data import, using a Windows form to kick off the import and show progress.
I've got this whole thing so nice and user friendly, with major and minor progress bars and everything... but just one problem... the form refresh keeps going AWOL.
I have a call to Form.Refresh() every time I update my labels/progress bars, and it usually starts off working. But if ever I need to break into debug mode, just to hand-hold the import a bit, the Refresh() call stops working, and sometimes even if I'm running without Debug mode, at some unpredictable point the same thing happens: the labels and progress bars do not get updated, and if you hide the form and reopen it, the form does not repaint at all - it just shows up entirely white.
Why, oh why, does Form.Refresh() stop working, and how can I fix this?
It sounds as if the import runs on the UI thread, which means that this thread is blocked, preventing the form from repainting itself. A better approach would be to use a BackgroundWorker component, do the import in the DoWork event handler and use the ProgressChanged to update the UI.
Example:
private void StartImport()
{
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.RunWorkerAsync();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// do some work simulating a lenghy process which occasionally
// reports progress with data back to the caller
for (int i = 0; i < 100; i++)
{
Thread.Sleep(200);
backgroundWorker.ReportProgress(i, "Item No " + i.ToString());
}
}
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
listBox.Items.Add(e.UserState.ToString());
}
Using this approach you will typically not need to call Refresh to force a repaint of the form.
You may want to change your code into using BeginUpdate and EndUpdate, like so:
Control.BeginUpdate();
// Do something to the control, e.g. add items or whatnot
Control.EndUpdate();
This way Refresh shouldn't be necessary.
AFAIK constantly calling Refresh is really a hack and should be avoiding, as it stresses the CPU quite a bit (it has to refresh everything instead of just the things which are changed).
Edit: If the form starts being white, it seems the drawing code is not been called at all, which indicates it's somewhat not responding.
I'd check the code for anything that can deadlock or otherwisely hang.
You could use observer pattern..in short if anything changes in model observer pattern will make sure that change is visible on form..
google it for some examples..
Depending on what .NET framework you're using, you can use the Task.Run approach:
private void btnShowProgress_Click(object sender, EventArgs e)
{
progressBar1.Value = 0;
Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = i; }));
}
});
}
Task.Run info
Using invoke with controls
The solution may not be the best practice but it definitely works for small applications.
In Form1 create a bool to check to see if the form is closed.
public bool formclosed = false
Then in Form2 on the Form Closing Event Handler add
formclosed = true
also in the Form2 after
InitializeComponent();
add
formclosed = false;
In Form1 create a timer.
In the timer1.Tick event handler say
private void timer1_Tick(object sender, EventArgs e)
{
if(formclosed == true)
{
Application.Restart();
}
}
This will restart the application and refresh everything ... I also had my text saved to the Properties.Settings.Default so everytime the application started the default settings would show.
I created an initial version of a Progress control using a BackgroundWorker. The Progress control computed and displayed nice things like Estimated Duration, Estimated Time to Completion. Each statistic was displayed by custom control based on a Label control. The Progress control worked in my production code.
Then I made some changes to the Progress control. I switched from basing my custom statistics controls from Label to Panel. I successfully ran all my tests (using NUnit). Then, I created a Test Windows Forms app. The Progress control successfully worked in the Test Windows Forms app.
When I ran my production Windows app with the updated Progress control it didn't display the statistics.
I tried Thread.Sleep(N), Form.Refresh(). None of those worked to update the statistics.
I eventually called Control.Invalidate() on each Statistic control. The Invalidate caused OnPaint to be called and the control happily updated the display with its specific statistic.
For Each Stat_Obj As Control in Statistics_Controls
Stat_Obj.Invalidate()
Next
You might need to give the window time to redraw itself. I understand you're doing the import in a loop and the loop is running on the main UI thread? Try adding this line to the loop:
Application.DoEvents();

Categories

Resources