Sorry for the stupid question but I can't get it to work.
I got a MainWindow that opens another window.
public static Window2 LoadWindow = new Window2();
public MainWindow()
{
InitializeComponent();
LoadWindow.Show();
Later in the code, I Start a function that creates a Background worker in the new window
if (MainWindow.Start == true)
{
MainWindow.LoadWindow.LoadImage(null, null);
MainWindow.Start = false;
}
public void LoadImage(object sender, RoutedEventArgs e)
{
worker = new BackgroundWorker();
...
And then I tried this to Change the Visibility of the MainWindow.
private void worker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
Application.Current.Dispatcher.Invoke(new Action(() => {
Application.Current.MainWindow.Visibility = Visibility.Visible;
}));
}
I thought Application.Current.MainWindow would point to my MainWindow but the debugger said that Window2 is the Current.MainWindow.
Actually, I am completely confused about the MainWindow.
Typically I initialize a class with a name and use it with that name. (e.g. Window2=class, LoadWindow=it's name)
But what is the name of the MainWindow or how can I interact with it from another window. It's so confusing when the MainWindow != the MainWindow >.<.
You could either inject LoadWindow with a reference to the MainWindow when you create it in the constructor, or you could get a reference to the MainWindow using the Application.Current.Windows collection:
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
if (mainWindow != null)
mainWindow.Visibility = Visibility.Visible;
Related
I am trying to implement some fade-in and fade-out animations for a user control in WPF. For the fade-in animation I was able to use the Loaded event to accomplish that.
public sealed partial class NowPlayingView : UserControl
{
public Duration AnimationDuration
{
get { return (Duration)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
public static readonly DependencyProperty AnimationDurationProperty =
DependencyProperty.Register("AnimationDuration", typeof(Duration), typeof(NowPlayingView), new PropertyMetadata(Duration.Automatic));
public NowPlayingView()
{
Opacity = 0;
InitializeComponent();
Loaded += NowPlayingView_Loaded;
Unloaded += NowPlayingView_Unloaded;
}
private void NowPlayingView_Unloaded(object sender, RoutedEventArgs e)
{
DoubleAnimation animation = new(1.0, 0.0, AnimationDuration);
BeginAnimation(OpacityProperty, animation);
}
private void NowPlayingView_Loaded(object sender, RoutedEventArgs e)
{
DoubleAnimation animation = new (0.0, 1.0, AnimationDuration);
BeginAnimation(OpacityProperty, animation);
}
}
I attempted to use the Unloaded event for the fade-out effect only to find out that the event is fired after the UserControl is removed from the visual tree (when the UserControl is no longer visible or accessible). Is there a way to run some code right before the UserControl "closes", something like the OnClosing event of a Window?
EDIT:
For a bit more context, the UserControl acts as a component of a more complex window. It is activated whenever the Property NowPlayingViewModel is not null and deactivated when null (which I do in order to hide the UserControl). It is when I set the ViewModel to null that I want to run the fade-out animation and I would like to keep the code-behind decoupled from other ViewModel logic.
<!-- Now playing View-->
<ContentControl Grid.RowSpan="3" Grid.ColumnSpan="2" Content="{Binding NowPlayingViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewmodels:NowPlayingViewModel}">
<views:NowPlayingView AnimationDuration="00:00:00.8" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
From my testing, I couldn't find any good solution to this so far, though I am open to suggestions that lead to similar behavior.
There is no Closing event in UserControl.. but you can get the parent window when UserControl is loaded and implement the fade-out behavior there..
First, Remove Unloaded += NowPlayingView_Unloaded;
Then, modify the Loaded code a bit..
private Window ParentWindow
{
get
{
DependencyObject parentDepObj = this;
do
{
parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
if (parentDepObj is Window parentWindow) return parentWindow;
} while (parentDepObj != null);
return null;
}
}
private void NowPlayingView_Loaded(object sender, RoutedEventArgs e)
{
DoubleAnimation animation = new(0.0, 1.0, AnimationDuration);
BeginAnimation(OpacityProperty, animation);
var parentWindow = this.ParentOfType<Window>();
parentWindow.Closing += WindowClosing;
}
private void WindowClosing(object sender, CancelEventArgs args)
{
var pw = ParentWindow;
pw.Closing -= WindowClosing;
args.Cancel = true;
var anim = new(1.0, 0.0, AnimationDuration);
anim.Completed += (s, _) => pw.Close();
BeginAnimation(OpacityProperty, anim);
}
Optional Note. You could replace the getter of ParentWindow property with a simple call
private Window ParentWindow => this.ParentOfType<Window>();
Where ParentOfType is an extension function in some public static class Utilities..
public static T ParentOfType<T>(this DependencyObject child) where T : DependencyObject
{
var parentDepObj = child;
do
{
parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
if (parentDepObj is T parent) return parent;
} while (parentDepObj != null);
return null;
}
This is my current App.xaml.cs
Its looks simple for one or two, but I have 7-8 windows.
Is there a clever way to make this a little more general and better?
public App()
{
_ViewModel = new MyAppViewModel();
_ViewModel.OpenXXXWindowEvent += new EventHandler(ViewModel_OpenXXXWindow);
_ViewModel.OpenYYYWindowEvent += new EventHandler(ViewModel_OpenYYYWindow);
...
}
private void ViewModel_OpenXXXWindow(object sender, EventArgs e)
{
_XXXWindow = new XXXWindow();
_XXXWindow.DataContext = _ViewModel;
_XXXWindow.ShowDialog();
}
private void ViewModel_CloseXXXWindow(object sender, EventArgs e)
{
if (_XXXWindow != null)
_XXXWindow.Close();
}
private void ViewModel_OpenYYYWindow(object sender, EventArgs e)
{
_YYYWindow = new YYYWindow();
_YYYWindow.DataContext = _ViewModel;
_YYYWindow.ShowDialog();
}
private void ViewModel_CloseYYYWindow(object sender, EventArgs e)
{
if (_YYYWindow != null)
_YYYWindow.Close();
}
...
Too much XAML code-behind is a signal that you're somehow breaking the MVVM pattern. A ViewModel receiving EventArgs is a no-no too.
To open/close dialogs I tend to use a messaging system, for example, the one provided by MVVM Light.
With a messaging system (using MVVM Light) you do something like this:
In your ViewModel:
private void SomeMethodThatNeedsToOpenADialog()
{
Messenger.Default.Send(new OpenDialogXMessage());
}
And in your View:
Messenger.Default.Register<OpenDialogXMessage>(this, (msg) => {
new DialogX().ShowDialog();
});
Some relevant links:
How to open a new window using MVVM Light Toolkit
Show dialog with MVVM Light toolkit
Well it's not a event handler solution, but how about a Binding solution? Unfortunately, you cannot bind Window.DialogResult, which causes the window to close, when the value is set. But you could create an AttachedProperty which can be bound to a property on the underlying ViewModel and sets the not bindable property, when its value is set. The AttachedProperty looks like this.
public class AttachedProperties
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached("DialogResult", typeof (bool?), typeof (AttachedProperties), new PropertyMetadata(default(bool?), OnDialogResultChanged));
private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var wnd = d as Window;
if (wnd == null)
return;
wnd.DialogResult = (bool?) e.NewValue; //here the not bindable property is set and the windows is closed
}
public static bool? GetDialogResult(DependencyObject dp)
{
if (dp == null) throw new ArgumentNullException("dp");
return (bool?)dp.GetValue(DialogResultProperty);
}
public static void SetDialogResult(DependencyObject dp, object value)
{
if (dp == null) throw new ArgumentNullException("dp");
dp.SetValue(DialogResultProperty, value);
}
}
The AttachedProperty can be used like this
<Window x:Class="AC.Frontend.Controls.DialogControl.Dialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hlp="clr-namespace:AC.Frontend.Helper"
hlp:AttachedProperties.DialogResult="{Binding DialogResult}">
<!-- put your content here -->
</Window>
Now you can use a Command to set the DialogResult property of the VM, which is the DataContext of the Window.
In my WPF application I would like to subscribe to some event/callbeck/whatever that tells me whenever a dialog window opens (and closes) in my application.
I found the window collection but this is a simple container and it doesn't seem to provide any means of subscription.
I also tried using event handlers but there seems not be an event that tells me what I need.
Any ideas?
One way to do it without a base class is adding a handler to MainWindow deactivated
If a new window is opened, the main window will lose focus = your "new window event"
private readonly List<Window> openWindows = new List<Window>();
public void ApplicationMainWindow_Deactivated(object sender, EventArgs e)
{
foreach (Window window in Application.Current.Windows)
{
if (!openWindows.Contains(window) && window != sender)
{
// Your window code here
window.Closing += PopupWindow_Closing;
openWindows.Add(window);
}
}
}
private void PopupWindow_Closing(object sender, CancelEventArgs e)
{
var window = (Window)sender;
window.Closing -= PopupWindow_Closing;
openWindows.Remove(window);
}
Without creating a Base class for all your windows where you can hook into the opened event (or manually adding the opened event to each window), I'm not sure how you'd be able to know when new windows were create.
There may be a more elegant way, but you could poll the Application.Current.Windows to see if any new windows were created while keeping track of the one's you've found.
Here is a crude example that will demonstrate how to use a DispatchTimer to poll for new windows, keep track of found windows and hook into the closed event.
Code Behind
public partial class MainWindow : Window
{
private DispatcherTimer Timer { get; set; }
public ObservableCollection<Window> Windows { get; private set; }
public MainWindow()
{
InitializeComponent();
// add current Window so we don't add a hook into it
Windows = new ObservableCollection<Window> { this };
Timer = new DispatcherTimer( DispatcherPriority.Background );
Timer.Interval = TimeSpan.FromMilliseconds( 500 );
Timer.Tick += ( _, __ ) => FindNewWindows();
Timer.Start();
this.WindowListBox.ItemsSource = Windows;
this.WindowListBox.DisplayMemberPath = "Title";
}
private void FindNewWindows()
{
foreach( Window window in Application.Current.Windows )
{
if( !Windows.Contains( window ) )
{
window.Closed += OnWatchedWindowClosed;
// inserting at 0 so you can see it in the ListBox
Windows.Insert( 0, window );
Feedback.Text = string.Format( "New Window Found: {0}\r\n{1}",
window.Title, Feedback.Text );
}
}
}
private void OnWatchedWindowClosed( object sender, EventArgs e )
{
var window = (Window)sender;
Windows.Remove( window );
Feedback.Text = string.Format( "Window Closed: {0}\r\n{1}",
window.Title, Feedback.Text );
}
private void CreateWindowButtonClick( object sender, RoutedEventArgs e )
{
string title = string.Format( "New Window {0}", DateTime.Now );
var win = new Window
{
Title = title,
Width = 250,
Height = 250,
Content = title,
};
win.Show();
e.Handled = true;
}
}
XAML
<Grid>
<ListBox Name="WindowListBox"
Width="251"
Height="130"
Margin="12,12,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
<TextBox Name="Feedback"
Width="479"
Height="134"
Margin="12,148,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
VerticalScrollBarVisibility="Auto" />
<Button Name="CreateWindowButton"
Width="222"
Height="130"
Margin="269,12,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="CreateWindowButtonClick"
Content="Create New Window"
FontSize="20" />
</Grid>
Click away and create as many new windows as you want; then close them. You'll see the feedback as it happens. Granted, there will be a 500ms delay whenever a new window is created since the DispatchTimer's interval is set at 500ms.
You could register a class handler in App.cs as demonstrated here
https://gist.github.com/mwisnicki/3104963
...
EventManager.RegisterClassHandler(typeof(UIElement), FrameworkElement.LoadedEvent, new RoutedEventHandler(OnLoaded), true);
EventManager.RegisterClassHandler(typeof(UIElement), FrameworkElement.UnloadedEvent, new RoutedEventHandler(OnUnloaded), true);
...
private static void OnLoaded(object sender, RoutedEventArgs e)
{
if (sender is Window)
Console.WriteLine("Loaded Window: {0}", sender);
}
private static void OnUnloaded(object sender, RoutedEventArgs e)
{
if (sender is Window)
Console.WriteLine("Unloaded Window: {0}", sender);
}
The link above seems to register an empty handler on instances to make things work properly.
I have never heard of any global open/close event.
It should somehow be possible to do, but that provides that you have control over all windows opening and closing. Like if you build a "base window" (which naturally inherit "Window") that all your dialogs windows inherit from.
Then you clould have a static event on the "base window" which you fire from the base window's opening and closing/closed (or unloaded) events, sending "this" as "sender".
You can attafh to that static event in your App.xaml.cs class.
It's a hack, but it's possible.
My application has several independent "top-level" windows, which all have completely different functions/workflows.
I am currently using ShowDialog() to make a WPF Window modal. The modal window is a child of one of the main windows. However, it is blocking all the top-level windows once it is open. I would like the dialog to block ONLY the parent window it was launched from. Is this possible?
I'm not sure if it matters, but the window that opens the dialog is the initial window of the app--so all other top-level windows are opened from it.
I had the same problem and implemented the modal dialog behavior as described in this post:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/820bf10f-3eaf-43a8-b5ef-b83b2394342c/windowsshowmodal-to-parentowner-window-only-not-entire-application?forum=wpf
I also tried a multiple UI thread approach, but this caused problems with third-party libraries (caliburn micro & telerik wpf controls), since they are not built to be used in multiple UI threads. It is possible to make them work with multiple UI threads, but I prefer a simpler solution...
If you implement the dialog as described, you can not use the DialogResult property anymore, since it would cause a "DialogResult can be set only after Window is created and shown as dialog" exception. Just implement your own property and use it instead.
You need the following windows API reference:
/// <summary>
/// Enables or disables mouse and keyboard input to the specified window or control.
/// When input is disabled, the window does not receive input such as mouse clicks and key presses.
/// When input is enabled, the window receives all input.
/// </summary>
/// <param name="hWnd"></param>
/// <param name="bEnable"></param>
/// <returns></returns>
[DllImport("user32.dll")]
private static extern bool EnableWindow(IntPtr hWnd, bool bEnable);
Then use this:
// get parent window handle
IntPtr parentHandle = (new WindowInteropHelper(window.Owner)).Handle;
// disable parent window
EnableWindow(parentHandle, false);
// when the dialog is closing we want to re-enable the parent
window.Closing += SpecialDialogWindow_Closing;
// wait for the dialog window to be closed
new ShowAndWaitHelper(window).ShowAndWait();
window.Owner.Activate();
This is the event handler which re-enables the parent window, when the dialog is closed:
private void SpecialDialogWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
var win = (Window)sender;
win.Closing -= SpecialDialogWindow_Closing;
IntPtr winHandle = (new WindowInteropHelper(win)).Handle;
EnableWindow(winHandle, false);
if (win.Owner != null)
{
IntPtr parentHandle = (new WindowInteropHelper(win.Owner)).Handle;
// reenable parent window
EnableWindow(parentHandle, true);
}
}
And this is the ShowAndWaitHelper needed to achieve the modal dialog behavior (this blocks the execution of the thread, but still executes the message loop.
private sealed class ShowAndWaitHelper
{
private readonly Window _window;
private DispatcherFrame _dispatcherFrame;
internal ShowAndWaitHelper(Window window)
{
if (window == null)
{
throw new ArgumentNullException("window");
}
_window = window;
}
internal void ShowAndWait()
{
if (_dispatcherFrame != null)
{
throw new InvalidOperationException("Cannot call ShowAndWait while waiting for a previous call to ShowAndWait to return.");
}
_window.Closed += OnWindowClosed;
_window.Show();
_dispatcherFrame = new DispatcherFrame();
Dispatcher.PushFrame(_dispatcherFrame);
}
private void OnWindowClosed(object source, EventArgs eventArgs)
{
if (_dispatcherFrame == null)
{
return;
}
_window.Closed -= OnWindowClosed;
_dispatcherFrame.Continue = false;
_dispatcherFrame = null;
}
}
One option is to start the windows that you don't want affected by the dialog on a different thread. This may result in other issues for your application, but if those windows do really encapsulate different workflows, that may not be an issue. Here is some sample code I wrote to verify that this works:
<Window x:Class="ModalSample.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Identifier}" Height="150" Width="150">
<StackPanel>
<TextBox Text="{Binding Identifier}" />
<Button Content="Open Normal Child" Click="OpenNormal_Click" />
<Button Content="Open Independent Child" Click="OpenIndependent_Click" />
<Button Content="Open Modal Child" Click="OpenModal_Click" />
</StackPanel>
</Window>
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace ModalSample
{
/// <summary>
/// Interaction logic for MyWindow.xaml
/// </summary>
public partial class MyWindow : INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
private int child = 1;
private string mIdentifier = "Root";
public string Identifier
{
get { return mIdentifier; }
set
{
if (mIdentifier == value) return;
mIdentifier = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Identifier"));
}
}
private void OpenNormal_Click(object sender, RoutedEventArgs e)
{
var window = new MyWindow {Identifier = Identifier + "-N" + child++};
window.Show();
}
private void OpenIndependent_Click(object sender, RoutedEventArgs e)
{
var thread = new Thread(() =>
{
var window = new MyWindow {Identifier = Identifier + "-I" + child++};
window.Show();
window.Closed += (sender2, e2) => window.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
private void OpenModal_Click(object sender, RoutedEventArgs e)
{
var window = new MyWindow { Identifier = Identifier + "-M" + child++ };
window.ShowDialog();
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I sourced this blog post for running a WPF window on a different thread.
How one can show dialog window (e.g. login / options etc.) before the main window?
Here is what I tried (it apparently has once worked, but not anymore):
XAML:
<Application ...
Startup="Application_Startup">
Application:
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
Window1 myMainWindow = new Window1();
DialogWindow myDialogWindow = new DialogWindow();
myDialogWindow.ShowDialog();
}
}
Outcome: myDialogWindow is shown first. When it is closed, the Window1 is shown as expected. But as I close Window1 the application does not close at all.
Here's the full solution that worked for me:
In App.xaml, I remove the StartupUri stuff, and add a Startup handler:
<Application x:Class="MyNamespace.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="ApplicationStart">
</Application>
In App.xaml.cs, I define the handler as follows:
public partial class App
{
private void ApplicationStart(object sender, StartupEventArgs e)
{
//Disable shutdown when the dialog closes
Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
var dialog = new DialogWindow();
if (dialog.ShowDialog() == true)
{
var mainWindow = new MainWindow(dialog.Data);
//Re-enable normal shutdown mode.
Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Current.MainWindow = mainWindow;
mainWindow.Show();
}
else
{
MessageBox.Show("Unable to load data.", "Error", MessageBoxButton.OK);
Current.Shutdown(-1);
}
}
}
Okay apologizes, here is the solution:
My original question worked almost, only one thing to add, remove the StartupUri from the Application XAML and after that add the Show to main window.
That is:
<Application x:Class="DialogBeforeMainWindow.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
Above, StartupUri removed.
Add myMainWindow.Show() too:
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
Window1 myMainWindow = new Window1();
DialogWindow myDialogWindow = new DialogWindow();
myDialogWindow.ShowDialog();
myMainWindow.Show();
}
}
WPF sets App.Current.MainWindow to the first window opened. If you have control over the secondary window constructor, just set App.Current.MainWindow = Null there. Once your main window is constructed, it will be assigned to the App.Current.MainWindow property as expected without any intervention.
public partial class TraceWindow : Window
{
public TraceWindow()
{
InitializeComponent();
if (App.Current.MainWindow == this)
{
App.Current.MainWindow = null;
}
}
}
If you don't have access, you can still set MainWindow within the main window's constructor.
If you put Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; into the constructor of the dialog, and add
protected override void OnClosed(EventArgs e) {
base.OnClosed(e);
Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
}
into the dialog class, you don't need to worry about making any changes to the default behaviour of the application. This works great if you want to just snap a login screen into an already-existing app without tweaking the startup procedures.
So you want to show one window, then another, but close down the app when that window is closed? You may need to set the ShutdownMode to OnMainWindowClose and set the MainWindow to Window1, along the lines ok:
Window1 myMainWindow = new Window1();
Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Application.Current.MainWindow = myMainWindow;
DialogWindow myDialogWindow = new DialogWindow();
myDialogWindow.ShowDialog();
here, do it like this. this will actaully change your main window and will work properly w/o having to change settings of your application object.
make sure to remove the event handler for application startup and to set your StartupUri in your app.xaml file.
public partial class App : Application
{
bool init = false;
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
if (!init)
{
this.MainWindow.Closing += new System.ComponentModel.CancelEventHandler(MainWindow_Closing);
init = true;
}
}
void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
Window toClose = this.MainWindow;
this.MainWindow = new Window2();
this.MainWindow.Show();
}
}
I have the same issue when i need to disloag a login screen before my main window
In you main window cunstructor add these lines
Application.Current.MainWindow = this;
Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
Resolve the main window or just call var mainWindow = new MainWindow()
Call the loginScreen.Show() or loginScreen.ShowDialog()