WPF - Pop a Loading window when busy - c#

I have to show a Window on an application that is not very well thought. The calls from every window are made from the same thread, no background workers. The application is rather yuge, so I can't just go around implementing BW pattern. Problem is, some of those actions take a while (around 5-10 seconds) and the whole app would turn Not Responding while waiting.
My idea was to implement some kind of watchdog on a background thread that would pop a window whenever I set a flag (App.Loading) in order to let the user know that something is going on.
In order to do that, I put this in the main menu
public Menu()
{
ShowInTaskbar = true;
InitializeComponent();
Debug.Ventana = "Menu";
App.Menu = this;
Loading = new Thread(new ThreadStart(CheckLoading));
Loading.SetApartmentState(ApartmentState.STA);
Loading.Start();
}
private void CheckLoading()
{
Loading load = new Andrei.Loading("Cargando datos")
{
ShowInTaskbar = false,
Topmost = true
};
while(true)
{
if (App.Loading && !load.IsVisible)
load.Show();
if (!App.Loading && load.IsVisible)
load.Hide();
}
}
Problem is, sometimes the window does not show up, and when it does, the animation isn't working (it's a small window with a string and an Indeterminate Progress Bar), which must be because the main thread is frozen by the while(true). I've tried putting the loop in yet another thread, but I can't access the window from another thread to hide and show it. I've tried putting a BW on the window to check for progress, but of course, after completing the first cycle, it doesn't go looking for the flag anymore.
Any other suggestions than completely redesigning the application to use BW for any and all time-taking processes?

Related

Showing Message for loading WPF

I have a WPF application that connects and disconnects to WiFi.
It takes around 2 seconds to complete the process. During that time, I want to show a simple waiting message on the top of my current window "Connecting.." or "Disconnecting...", which closes as soon is the process is complete.
What should I use?
MessageBox is not working because a) it has a button, and b) I can't just close it at my own will through code (I think).
When the Wi-Fi starts to connect, use:
MyWindow popup = new MyWindow();
popup.ShowDialog();
Where MyWindow is a small form containing information.
And when the Wi-Fi is connected, use:
popup.Close();
ShowDialog() prevents user interaction with the parent form.
You should do the long-running task that takes two seconds to complete on a background thread and show and close the window on the dispatcher thread, e.g.:
Window window = new Window()
{
WindowStyle = WindowStyle.None,
Content = new TextBlock { Text = "working..." }
};
Task.Run(() =>
{
//do something that might take a while here...
System.Threading.Thread.Sleep(2000);
}).ContinueWith(task => window.Close(),
CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
window.ShowDialog(); //Call .Show() instead if you don't want to block here until the task has finished.
For this sort of thing I use a busy "throbber" or "spinner" which is shown in a panel on top of the window. That just animates to show something is going on.
I bind the visibility of that to an "IsBusy" property in my window's viewmodel.
In a base viewmodel that inherits from, because this is a common requirement.
When I set Isbusy true, the spinner appears and animates.
When I set it False, the spinner disappears.
I also have an area where I show messages. These are animated so they appear and then fade out after a few seconds.
It's a bit much code to paste into a SO answer, but there's working code uses this approach here:
https://gallery.technet.microsoft.com/WPF-Entity-Framework-MVVM-78cdc204
Throbber is the usercontrol to look at.
This usually goes in a panel covers where the user would edit stuff - so they can't change data as it's being updated.
I also use IsBusy in the CanExecute predicate of commands to disable buttons etc as processing is done. Because notification isn't necessarilly instant I check inside all commands and exit if IsBusy is true. Commands set IsBusy true whilst they run.
For completeness and probably not directly relevant to Bella's question.
In some situations I want to just show the user that processing is happening but allow them to continue working. This happens a fair bit in the Map and Scenario editors of the game I'm working on.
For example, when you mouse over a commander there's a Spatial A* best route calculation happens between a commander his subordinates that can take in the order of 1 to 12 seconds. I don't want to block the user but I want to show something is going on in this instance.
I have a second mechanism with a less intrusive throbber I use for this. That appears next to my user notification message.

Hosting multiple instance of app in a tabcontrol/ queuing

So I have This app with a MainForm that has a few buttons on it. and the buttons would do time consuming tasks that includes working with MainForm UI. Because some times we need to run multiple instances of this app at the sametime I decided to Create a MainFormHost where it is a form with a tab control which under each tab I create an instance of my MainForm and host it there. And so far everything was ok. The problem is when I click on a button on MAinForm1 it starts working fine but as soon as I click on a button on MainForm2 the process of MainForm1 button gets queued behind the MainForm2 process.
MainForm GetMainFrom(TabPage tabPage)
{
tabPage.Invoke(new Action(() =>
{
mainForm = new MainForm();
mainForm.TopLevel = false;
mainForm.FormBorderStyle = FormBorderStyle.None;
mainForm.Dock = DockStyle.Fill;
_mainForms.Add(mainForm);
tabPage.Controls.Add(mainForm);
mainForm.Show();
}));
}
And then call the method:
var mainFormThread = new Thread(() =>
{
mainForm = GetMainFrom(tabPage);
});
mainFormThread.SetApartmentState(ApartmentState.STA);
mainFormThread.Start();
I cannot change the code inside the MainForm to Invoke things when they want to work with the UI because it is going to be too much of work but I can easily make each button click to be called from a thread/task or...
TIA
In short, you can only have one 'main thread' that can interact with the UI controls. Ever. Period. There is no way to get around this (in standard code, you can draw to your form from a different process, but I won't go into that)
So, what you need to do is arrange your code in a set pattern.
Methods that do background work - this can be done on a separate thread(s)
Methods that Update the UI - MUST be done on the UI/Main Thread
The idea being simple, background work takes a long time, and updating the UI shouldn't.
Because you have 1 or 2 long running actions running ON THE MAIN UI THREAD, this 'blocks the UI' and causes the behavior your are experiencing.
By blocks the UI, what you actually have is the Windows Message pump can't pump. So every single command to resize a window or update a control on a form is a message in the pump. That must run on the main thread. If you do 'work' on that thread, such as connecting to a database, download a file etc., then the pump can't continue, so you get the 'this application is not responding' message.
So, you must download the file or whatever on a thread, and when it's finished, transition to the UI thread, and update the UI, e.g. say finished in a text box.
The way to transition between the two threads (background to main) is to use begininvoke, and you know if this is needed by using 'invoke required' .
From MSDN
// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control.
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextCallback and calls itself asynchronously using the
// Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
This is a great article and will get you where you need to be:
MSDN about thread safe code and invoke required

Is there a way to not have a WPF MainWindow method terminating to avoid threading?

I'm currently working on a WPF project that is trying to continuously update a listbox based on a network streamed source.
It is my understanding that a initializeComponent() method will only actually display the WPF window once the MainWindow() method has terminated. However I am trying to have a while(true) loop inside it in order to continuously listen for update signals from a server and update the listbox with appropriate values.
Each individual method is working fine, it's just that it doesn't open the WPF window in its current form due to the while loop. I am trying to avoid using a background update thread in order to update the list box because I am aware that I would have to implement functionality in order to pass the thread "ownership" of the listbox to the thread and I'm not 100% sure of how to do this.
Is there a workaround, or better yet, is there something obvious that I'm missing to achieve my required functionality?
The code is as follows:
public MainWindow()
{
TcpClient client = new TcpClient();
client.Connect(serverAddress, port);
NetworkStream stream = client.GetStream();
numberOfPumps = 0; //initialize as 0 on startup.
handshake(stream);
InitializeComponent();
updatePumpList(stream);
updateListBox();
while(true)
{
updatePumpList(stream);
updateListBox();
}
}
The updateListBox() method is simply adding items to the listbox from a dictionary.
private void updateListBox()
{
foreach(KeyValuePair<string, PumpItem> kvp in pumpDict)
{
pumpListBox.Items.Add(kvp.Key + ": " + kvp.Value.state);
}
}
Unfortunately, you just can't do it the way you want to. In a Windows application, you must let the main (UI) thread run. Any looping you do on that thread will hang the whole application until it's done. If the window's up, that looks like the window is frozen (because it is). If it's not up yet, it looks like a wait cursor, forever. No way around that. The thread has to be left alone to process input, update the window, etc. Even manually pumping the message loop (anybody remember MFC?) is a poor expedient. Windows applications work best if you leave the main thread to do its thing as the designers intended.
We do a lot of stuff on the main thread of course, but it's quick stuff that hands control back before the user notices any latency. Synchronous Internet access is never quick enough, and a polling loop that lasts for the lifetime of your process is out of the question.
You've got two options here, both of them pretty anodyne in practice.
You could use a DispatchTimer, with asynchronous internet access.
The other is the worker thread you're trying to avoid. They're not that bad. Just keep a reference to the Thread object around to abort on program shutdown, and the thread has to "invoke into" the UI thread when it does anything that'll touch any UI (including setting any property that will raise a PropertyChanged event).
It's not a big deal at all:
Action act = () => Status = newStatus;
App.Current.Dispatcher.Invoke(act);
Have you considered setting up an onLoad event handler for your WPF window which will trigger once the WPF window is displayed? The event handler could then run your while loop accordingly.
Alternatively, you could have a timer primed to fire an event a few moments after the end of the constructor, allowing the window to display and then the while loop to begin.

ToolStripProgressBar doesn't repaint when sleeping immediately afterwards

I have only a little experience with threading in WinForms. I am trying to set a progress bar to 100, sleep for 2 seconds, and then hide the progress bar.
I have:
public void ProgressBarTimerStop()
{
if (!AnyWorkersBusy())
{
if (InvokeRequired)
{
Invoke((MethodInvoker)delegate
{
ProgressBarTimer.Stop();
Thread.Sleep(1000);
Application.DoEvents();
Thread.Sleep(1000);
StatusToolStripProgressBar.Visible = false;
StatusToolStripProgressBar.Value = StatusToolStripProgressBar.Minimum;
});
}
else
{
ProgressBarTimer.Stop();
StatusToolStripProgressBar.Value = StatusToolStripProgressBar.Maximum;
Thread.Sleep(1000);
Application.DoEvents();
Thread.Sleep(1000);
StatusToolStripProgressBar.Visible = false;
StatusToolStripProgressBar.Value = StatusToolStripProgressBar.Minimum;
}
}
}
For the current problem I am experiencing InvokeRequired is false.
When I call the line:
StatusToolStripProgressBar.Value = StatusToolStripProgressBar.Maximum;
my watch window shows the value as set, but the GUI has not updated to reflect this. If I immediately sleep for a full two seconds the GUI does not update before sleeping.
If I call Application.DoEvents() without calling sleep beforehand -- it appears to do nothing.
If I sleep, then call DoEvents -- the GUI updates, and then I go back to sleep. This is correct, but I feel like my code is heinous.
Am I misunderstanding a key concept here?
EDIT: ProgressBarTimerStop is called only through "RunWorkerCompleted" event handlers.
I tried this, but it had no effect:
StatusToolStripProgressBar.GetCurrentParent().Refresh();
The 'key concept' that you are missing is that updating the progress bar's value does not immediately tell the progress bar to redraw itself.
When you update the progress bar, the GUI thread repaints the progress bar on the next window repaint. This only happens when the GUI thread becomes free to do so. Normally this happens when the window receives a paint message from the operating system. This should be picked up by Application.DoEvents, but it could be that the message isn't queued quickly enough to be picked up by DoEvents. You may be able to force it to with a Refresh call.
Call StatusToolStripProgressBar.ProgressBar.Refresh(); on every step. ProgressBar property of ToolStripProgressBar is hidden and it may not be shown in intellisense list.

Toggling Picture Box visibility C#

Why is the picture box control's visibility property not working here. I have initially set them to false, so that when the screen loads they are not visible. But then I wish to toggle this. I have done the following but does not seem to work. This is a windows forms application.
private void Action()
{
while (true)
{
Random r1 = new Random();
int num = r1.Next(1,3);
switch (num)
{
case 1:
pictureBoxLeft.Visible = true;
pictureBoxRight.Visible = true;
break;
case 2:
pictureBoxLeft.Visible = true;
pictureBoxRight.Visible = false;
break;
case 3:
pictureBoxLeft.Visible = false;
pictureBoxRight.Visible = true;
break;
}
Thread.Sleep(200);
pictureBoxLeft.Visible = false;
pictureBoxRight.Visible = false;
Thread.Sleep(500);
}
}
Also to add, this is working properly with a text box!!! Any Ideas???
Many thanks in advance
Setting the Visible property to true doesn't show the control, it only creates a message that will show it. As long as you are keeping the main thread busy with a Sleep, it will not process any messages, and the controls won't be shown.
You should show the picture boxes and then set up a timer with code that will hide the picture boxes when the timer's tick event is triggered. Then you exit your method so that the main thread can handle the messages that will show the picture boxes.
This is your UI thread. UI thread is so busy that it is not getting time to refresh the display. UI thread is busy in endless while loop, so how can it update UI?
In addition to Thread.Sleep(); call Application.DoEvents() to trigger event handling/ui updating. However ensure you're not calling Action() again somehow. A simple Thread.Sleep() won't allow the UI to update as you're still in the handler and the framework won't be able fire any new events on its own as it won't know if you're finished when waiting (due to the thread still being busy).
Overall your approach might be a bad idea (depending on other code etc.). Consider using timers or a background worker instead.
Whenever the UI changes does not apear after you did some changes, always do Refres(). e.g. pictureBoxLeft.Refresh();. This will always make UI changes appear. Please let me know if it works for you.

Categories

Resources