Why gif image do not animate in modeless window? - c#

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

Related

What is the previous window called in WPF

I know the current window can be used with "this" but is there anything I can use to call the previous window?
For example I have this code going off when I press a button
Buyer_Login BuyerWindow = new Buyer_Login();
Visibility= Visibility.Hidden;
BuyerWindow.Show();
I need to be able to go back to the first window and I need to close the BuyerWindow and I was going to do it with this.Close();
What can I do to make the first window's visibility visible again?
You could handle the Window.Closed event:
MainWindow.xaml.cs
private void OnClick(object sender, EventArgs e)
{
var loginWindow = new BuyerLogin();
loginWindow.Closed += OnBuyerLoginWindowClosed;
this.Visibility = Visibility.Hidden;
loginWindow.Show();
}
private void OnBuyerLoginWindowClosed(object sender, EventArgs e)
=> this.Visibility = Visibility.Visible;
You should consider to show the login window from the App.xaml.cs before you show your main window (recommended):
App.xaml.cs
private async void App_OnStartup(object sender, StartupEventArgs e)
{
var loginWindow = new BuyerLogin();
bool? dialogResult = loginWindow.ShowDialog();
if (dialogResult.GetValueOrDefault())
{
var mainWindow = new MainWindow();
mainWindow.Show();
}
}
App.xaml
<Application Startup="App_OnStartup">
<Application.Resources>
</Application.Resources>
</Application>
There is a collection of open windows.
App.Current.Windows;
It depends on exactly what you're doing opening windows.
If you start up then mainwindow will be [0] in that collection.
Say you then open an instance of window1.
That in turn opens an instance of window2.
There is a bit of a complication if you f5 in visual studio because it opens adorner windows.
Setting that aside for a moment.
When I write code to do what I describe above.
In Window2 I handle content rendered:
public partial class Window2 : Window
{
public Window2()
{
InitializeComponent();
}
private void Window_ContentRendered(object sender, EventArgs e)
{
var wins = App.Current.Windows;
wins[1].Close();
}
}
That instance of Window1 is closed.
Your new window is very likely the last window in that zero based collection and the previous one the window before that.
You could perhaps search the collection and find index for "this" and subtract one if you're doing more complicated things.
The chances are though, you want to close the window indexed by the count of that collection minus 2. Because it's zero based.
With my exploratory code, window1 closes with this:
private void Window_ContentRendered(object sender, EventArgs e)
{
var wins = App.Current.Windows;
wins[wins.Count - 2].Close();
}
Personally, I prefer single window apps and switch out the content in part of mainwindow. Leaving navigation buttons etc static in mainwindow.
If you're effectively opening one other window and closing the previous to do things then maybe you could consider a single window app instead.

WPF - Go back to first window from second window

I have a MainWindow page and a LoadingWindow. The MainWindow has a button that closes itself and opens LoadingWindow. What I want is when the user closes the Loading window to go back to the MainWindow as a new instance.
MainWindow.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
LoadingWindow load = new LoadingWindow(calibration , "MainWindow" , this);
Mouse.OverrideCursor = null;
Application.Current.MainWindow.Close();
load.ShowDialog(); // Exception is here the next time it is called
}
LoadingWindow.xaml.cs
private void Close_Button(object sender, RoutedEventArgs e)
{
MainWindow main = new MainWindow();
this.Close();
main.ShowDialog();
}
Now when I try to close the Loading window and press the Button_Click, the following error shows up at the load.ShowDialog() although I am declaring a new instance of it.
System.InvalidOperationException: Cannot set Visibility or call Show, ShowDialog, or WindowInteropHelper.EnsureHandle after a Window has closed
I read that you cannot open a window after you closed it, but I am having a new instance which should not make this problem.
On your main window don't use the Application.Current.MainWindow instance, use this.Close() instead.
private void Button_Click(object sender, RoutedEventArgs e)
{
LoadingWindow load = new LoadingWindow(...);
Mouse.OverrideCursor = null;
//Application.Current.MainWindow.Close();
this.Close();
load.ShowDialog(); // Exception is here the next time it is called
}
Refer to this thread & this for clarification on the difference.

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();
}

How to bring MainWindow into view when closing a window?

I'm trying to bring my MainWindow into view when I close a specific window within the same application. I have tried to do this but with the code I have created it just creates a new instance of MainWindow and I end up having 2 MainWindows instead of a desired one. Here is the code below that I have got.
private void Weight_Click(object sender, RoutedEventArgs e)
{
try
{
MultipleConverters.Windows.Weight WeightCalculation = new Windows.Weight();
WeightCalculation.Show();
this.WindowState = WindowState.Minimized;
}
// This code above works fine and minimizes the mainwindow and brings into view the selected window.
private void Quit_Click(object sender, RoutedEventArgs e)
{
this.Close();
MainWindow bringIntoView = new MainWindow();
bringIntoView.Show();
}
// Now with this code above is the problem code. This code is within the new window and what Iam trying to achieve is when this window is closed the mainwindow will be brought back into scope rather than creating a new instance of it, and leaving me with 2 Mainwindows rather than the desired 1 Mainwindow. any help would be great.
Use the Owner property to store the reference to the main window, you can then use that property to bring the window back up.
private void Weight_Click(object sender, RoutedEventArgs e)
{
try
{
MultipleConverters.Windows.Weight WeightCalculation = new Windows.Weight();
WeightCalculation.Owner = this;
WeightCalculation.Show();
this.WindowState = WindowState.Minimized;
}
elsewhere
private void Quit_Click(object sender, RoutedEventArgs e)
{
this.Close();
Owner.WindowState = WindowState.Normal;
}
However based on the behavior you are showing you may want to look in to using ShowDialog() instead of minimizing the parent window and use that instead.
private void Weight_Click(object sender, RoutedEventArgs e)
{
try
{
MultipleConverters.Windows.Weight WeightCalculation = new Windows.Weight();
WeightCalculation.Owner = this;
WeightCalculation.ShowDialog(); //The code pauses here till the dialog is closed.
}
Application.Current.MainWindow.Activate();
There is a handy property Application.Current.MainWindow that you can use to access the main window declared in App.xaml, you should just be able to show it by calling:
Application.Current.MainWindow.Show();
Application.Current.MainWindow.Activate();
To simplify things, you could create a static method on your MainWindow which handles all this:
public static void TryReveal()
{
var mainWindow = Application.Current.MainWindow;
if (mainWindow == null)
{
// The main window has probably been closed.
// This will stop .Show() and .Activate()
// from throwing an exception if the window is closed.
return;
}
if (mainWindow.WindowState == WindowState.Minimized)
{
mainWindow.WindowState = WindowState.Normal;
}
// Reveals if hidden
mainWindow.Show();
// Brings to foreground
mainWindow.Activate();
}
And then your other windows can just call MainWindow.TryReveal(). That way your windows don't need any reference to the main window as the static method handles it.
The best way you could handle this in WPF though is (I think) using a messaging implementation (eg. MVVM Light's Messaging system, or Caliburn.Micro's EventAggregator). Your MainWindow would subscribe to a "MainWindowViewStateMessage" or something like that (defined by you) and your other windows would pass it through the messaging system. The main window would intercept it and do the necessary work.
private void Quit_Click(object sender, RoutedEventArgs e)
{
this.Close();
MainWindow bringIntoView = new MainWindow();
bringIntoView.Show();
}
You're creating a new instance of MainWindow and then showing it. This is why a new MainForm is shown.
One thing you can do is set a property on the WeightCalculation window like this:
public MainWindow _mainWindow { get; set; }
Before showing the WeightCaculation, set _mainWindow to your current instance of MainWindow :
MultipleConverters.Windows.Weight WeightCalculation = new Windows.Weight();
WeightCalculation._mainWindow = this;
WeightCalculation.Show();
this.WindowState = WindowState.Minimized;
and from the new form you can now interact with the MainWindow.

Dispatcher with multi thread

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

Categories