Dispatcher with multi thread - c#

I have a multi thread application with a background worker which I use to display a splash screen for processing the creation of the main window.
I want to update the progress bar in the thread 'u' in my program, so I will have to invoke the progressbar control each time I want to update it from the thread 'u'.
So that means I don't have to use "backgroundWorker_DoWork" especially.
The problem I have is that I can't display the mainwindow (form2) when "backgroundWorker_RunWorkerCompleted" event is called.
I think the problem is about the dispatcher.
public partial class App : Application
{
public BackgroundWorker backgroundWorker;
private SplashScreenWindow splashScreen;
public static EventWaitHandle initWaitHandle = new AutoResetEvent(false);
public MainWindow Form2 { get; set; }
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();
protected override void OnStartup(StartupEventArgs e)
{
backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
splashScreen = new SplashScreenWindow();
splashScreen.ShowInTaskbar = false;
splashScreen.ResizeMode = ResizeMode.NoResize;
splashScreen.WindowStyle = WindowStyle.None;
splashScreen.Topmost = true;
splashScreen.Width = (SystemParameters.PrimaryScreenWidth) / 2.5;
splashScreen.Height = (SystemParameters.PrimaryScreenHeight) / 2.5;
splashScreen.Show();
base.OnStartup(e);
backgroundWorker.RunWorkerAsync();
Thread u = new Thread(new ThreadStart(interface_process));
u.SetApartmentState(ApartmentState.STA);
u.Start();
}
public void interface_process()
{
MainWindow form2 = new MainWindow();
this.Form2 = form2;
System.Windows.Threading.Dispatcher.Run();
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Invoke((Action)delegate()
{
this.Form2.Show(); // does not work
System.Windows.Threading.Dispatcher.Run();
});
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
splashScreen.ValueProgressBar = e.ProgressPercentage;
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i += 10)
{
backgroundWorker.ReportProgress(i, "Chargement en cours : " + i);
Thread.Sleep(500);
}
}
}

It's not clear to me how the code you posted would even compile, as the WPF Window class does not have an Invoke() method on it. That would cause a compile-time error in the code here:
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Invoke((Action)delegate()
{
this.Form2.Show(); // does not work
System.Windows.Threading.Dispatcher.Run();
});
}
If the above code is changed so that the second statement in the method reads this.Form2.Dispatcher.Invoke((Action)delegate() — that is, use the Dispatcher object that owns the Form2 object — not only should the code compile, but the call this.Form2.Show() should also work. Note that the second call to Dispatcher.Run() is not needed and in fact should be avoided.
The "correct" implementation of the method thus would look like this:
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Dispatcher.Invoke((Action)delegate()
{
this.Form2.Show(); // works fine
});
}
Now, that said…it seems to me that the whole approach is flawed. You really should just have the one UI thread in your program. If you want something to happen first while a splash screen is shown, then make the splash screen window the first window that's shown, run the background task, and then show the main window in the same thread when it's done.
Here is an example of what you seem to be trying to do, but written to use just the main UI thread and the normal WPF startup mechanisms…
Let's assume we start with a plain WPF project in Visual Studio. This will have in it already an App class and a MainWindow class. We just need to edit it to do what you want.
First, we need a splash screen window. Most of the configuration can be done in XAML; because you want the width and height computed based on the screen size, it's easiest (for me) to just put that in the constructor. This winds up looking like this:
SplashScreenWindow.xaml:
<Window x:Class="TestSingleThreadSplashScreen.SplashScreenWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
ShowInTaskbar="False"
ResizeMode="NoResize"
WindowStyle="None"
Topmost="True"
Title="SplashScreenWindow">
<Grid>
<ProgressBar HorizontalAlignment="Left" Height="10" Margin="10,10,0,0"
VerticalAlignment="Top" Width="100"
Value="{Binding ValueProgressBar}"/>
</Grid>
</Window>
SplashScreenWindow.xaml.cs:
public partial class SplashScreenWindow : Window
{
public readonly static DependencyProperty ValueProgressBarProperty = DependencyProperty.Register(
"ValueProgressBar", typeof(double), typeof(SplashScreenWindow));
public double ValueProgressBar
{
get { return (double)GetValue(ValueProgressBarProperty); }
set { SetValue(ValueProgressBarProperty, value); }
}
public SplashScreenWindow()
{
InitializeComponent();
Width = SystemParameters.PrimaryScreenWidth / 2.5;
Height = SystemParameters.PrimaryScreenHeight / 2.5;
}
}
Now the above class is the one we want shown first. So we edit the App class's XAML to do that, by changing the StartupUri property:
<Application x:Class="TestSingleThreadSplashScreen.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="SplashScreenWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Finally, we need our App class to run the BackgroundWorker, doing the appropriate things at various times:
public partial class App : Application
{
private SplashScreenWindow SplashScreen { get { return (SplashScreenWindow)this.MainWindow; } }
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
backgroundWorker.RunWorkerAsync();
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
new MainWindow().Show();
SplashScreen.Close();
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
SplashScreen.ValueProgressBar = e.ProgressPercentage;
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker backgroundWorker = (BackgroundWorker)sender;
for (int i = 0; i <= 100; i += 10)
{
backgroundWorker.ReportProgress(i, "Chargement en cours : " + i);
Thread.Sleep(500);
}
}
}
The only tricky thing in there is that the splash screen window must be closed after you show the main window. Otherwise, WPF will think you've closed the last window (well, technically you would have :) ) and will shut down the program. By showing the main window before closing the splash screen window, the program continues to run.
In my opinion, this is a much better way to do things, as it works with the normal WPF mechanisms, rather than trying to subvert and/or work around them. It takes advantage of the Dispatcher that is created automatically when the program starts, doesn't require an extra UI thread, etc. Oh, and…it works. So there's that. :)

Related

Closing of a WPF Window still visible in taskbar

I have a small issue with my WPF application.
I have a splash image (as a XAML Window) and the main app (as another XAML window that gets caleld from Splash)
Even when I use this .Close() on Splash window and work on Main app, the Splash window is still visible in taskbar.
If I were to use this .Close on main app I would have two blank windows in taskbar application that I have to press X to close completely.
I've tried with Application.Current.Shutdown() as well but results were same.
Splash:
public Splash()
{
InitializeComponent();
}
private void Window_ContentRendered(object sender, EventArgs e)
{
CloseProcedure();
}
public async void CloseProcedure()
{
//Missing data loading which will be async.
UC_Main UCMain = new UC_Main();
MainWindow window = new MainWindow();
window.AppContent.Children.Add(UCMain);
this.Close();
await Task.Delay(500); //for fade effect.
window.Show();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Closing -= Window_Closing;
e.Cancel = true;
var anim = new DoubleAnimation(0, (Duration)TimeSpan.FromSeconds(0.5));
this.BeginAnimation(UIElement.OpacityProperty, anim);
anim.Completed += (s, _) => this.Close();
}
Main App
private void BTN_Close_MouseUp(object sender, MouseButtonEventArgs e)
{
this.Close();
}
private void titleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
titleBar.Background = Brushes.White;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Closing -= Window_Closing;
e.Cancel = true;
var anim = new DoubleAnimation(0, (Duration)TimeSpan.FromSeconds(0.2));
this.BeginAnimation(UIElement.OpacityProperty, anim);
anim.Completed += (s, _) => Application.Current.Shutdown();
}
}
The default shutdown mode of a WPF app is "OnLastWindowClose" which means as soon as the last window is closed, the applicaton shutdown procedure will be called, so you should not need to explicitly write System.Windows.Application.Shutdown().
You probably have something referencing the splash screen that isn't being closed off. When you open your main window, you should close the splash screen properly at that time and ensure that any reference to it is null.
If you post all your code, it will be easier to know the correct fix.
To prevent a window showing in the taskbar in the first place, set the ShowInTaskbar property of your Window XAML declaration:
<Window ShowInTaskbar="False" ...
I've come up with a solution for this.
Instead of trying to shut down the process and animate the fade inside the closing event handler, I wrote an asynchronous method that will fade out the screen, await for the same amount of time and then kill the process. Works like a charm.
private void BTN_Close_MouseUp(object sender, MouseButtonEventArgs e)
{
this.FadeOut();
}
async private void FadeOut()
{
var anim = new DoubleAnimation(0, (Duration)TimeSpan.FromSeconds(0.2));
this.BeginAnimation(UIElement.OpacityProperty, anim);
await Task.Delay(200);
this.Close();
System.Diagnostics.Process.GetCurrentProcess().Kill();
}

Odd behavior when creating a new window in WPF using another thread

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.)

Event not raising properly in forms

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...

Why gif image do not animate in modeless window?

To show animated GIF in WPF/C# I use this code sample in Microsoft MSDN : Show GIF animation in WPF.
When I use this in modeless window (window.Show()), the image do not animate. Why?
With window.ShowDialog() (modal window) it works correctly.
In WPF Project Befor Start MainWindow i show a window to do my first task in modeless and then close it.(these are in app.xaml.cs Startup event)
// app.xaml.cs
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
FirstTask firstTask = new FirstTask();
firstTask.Show();
// do task
System.Threading.Thread.Sleep(5000);
firstTask.Close();
MainWindow mainWindow = new MainWindow();
mainWindow.ShowDialog();
}
}
I add below code end of AnimatedGIFControl_Loaded function in AnimatedGIFControl class to start animate gif automatically.
ImageAnimator.Animate(_bitmap, OnFrameChanged);
complete AnimatedGIFControl_Loaded code
void AnimatedGIFControl_Loaded(object sender, RoutedEventArgs e)
{
// Get GIF image from Resources
if (gifanimate.Properties.Resources.ProgressIndicator != null)
{
_bitmap = gifanimate.Properties.Resources.ProgressIndicator;
Width = _bitmap.Width;
Height = _bitmap.Height;
_bitmapSource = GetBitmapSource();
Source = _bitmapSource;
ImageAnimator.Animate(_bitmap, OnFrameChanged);
}
}
ImageAnimator.Animate(_bitmap, OnFrameChanged);
Also i add to firstTask window and MainWindow to show animated gif.
Another problem: after firstTask.Close(); application do not show MainWindow. did you know why?
Show() method does not block the call and continues with the execution,
ShowDialog() method blocks the call and waits with the execution until the modal dialog is closed (and also during that,all UI messages are dispatched)
I fond a solution for that.
// app.xaml.cs
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private FirstTask firstTask;
private void Application_Startup(object sender, StartupEventArgs e)
{
BackgroundWorker backWorker = new BackgroundWorker();
backWorker.DoWork += new DoWorkEventHandler(backWorker_DoWork);
backWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backWorker_RunWorkerCompleted);
backWorker.RunWorkerAsync();
MainWindow mainWindow = new MainWindow();
firstTask = new FirstTask();
firstTask.ShowDialog();
mainWindow.Show();
}
void backWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
firstTask.Close();
}
void backWorker_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(5000);
}
}
I use BackgroundWorker to do my tasks in background. after complete tasks close firstTask and show mainWindow.
important attention: For showing mainWindow after closed first task, mainWindow should declare before firstTask.ShowDialog().

Opening A WinForm Programmatically In Another WinForm's Code

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?

Categories