I wanted to have a customized window so followed a few tutorials which enable this by setting the window style to none, and then adding the title-bar/restore/minimize/close buttons yourself. The minimize is achieved by simply handling the click event and setting the Window-state to minimized, but this doesn't show the minimize animation you see on Windows 7, and just instantly hides the window, which feels very odd when used with other windows that do animate, as you tend to feel the application is closing.
So, is there anyway of enabling that animation? .. it seems to be disabled when you change the WindowStyle to none.
Edit : Test code
public partial class MainWindow : Window
{
public MainWindow()
{
WindowStyle = WindowStyle.None;
InitializeComponent();
}
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
// this doesnt seem to animate
SendMessage(new WindowInteropHelper(this).Handle, 0x0112, (IntPtr)0xF020, IntPtr.Zero);
}
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
{
base.OnMouseRightButtonDown(e);
WindowStyle = WindowStyle.SingleBorderWindow;
WindowState = WindowState.Minimized;
}
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => WindowStyle = WindowStyle.None));
}
}
A newer feature of .NET has solved this problem.
Leave your WindowStyle="SingleBorder" or "ThreeDBorder"
Leave ResizeMode="CanResize"
Then add this to the xaml inside the
<Window>
<WindowChrome.WindowChrome>
<WindowChrome GlassFrameThickness="0" CornerRadius="0" CaptionHeight="0" UseAeroCaptionButtons="False" ResizeBorderThickness="7"/>
</WindowChrome.WindowChrome>
</Window>
The window will not have any of the default border, but will still allow resizing and will not cover the task bar when maximized. It will also show the minimize animation as before.
EDIT
Unfortunately, when using WindowStyle="None" it still disables the animation and covers the taskbar. So this method does not work if you're trying to make a transparent window.
Edited the answer after experimenting a bit.
There are two options:
1. You can change the Style just before minimising and activating the window:
private void Button_OnClick(object sender, RoutedEventArgs e)
{
//change the WindowStyle to single border just before minimising it
this.WindowStyle = WindowStyle.SingleBorderWindow;
this.WindowState = WindowState.Minimized;
}
private void MainWindow_OnActivated(object sender, EventArgs e)
{
//change the WindowStyle back to None, but only after the Window has been activated
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => WindowStyle = WindowStyle.None));
}
This solution has one limitation - it doesn't animate the window if you minimise it from the taskbar.
2. Minimise the Window by sending it WM_SYSCOMMAND message with SC_MINIMIZE parameter and changing the border style by hooking into the message (HwndSource.FromHwnd(m_hWnd).AddHook(WindowProc)).
internal class ApiCodes
{
public const int SC_RESTORE = 0xF120;
public const int SC_MINIMIZE = 0xF020;
public const int WM_SYSCOMMAND = 0x0112;
}
private IntPtr hWnd;
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
private void Window_Loaded(object sender, RoutedEventArgs e)
{
hWnd = new WindowInteropHelper(this).Handle;
HwndSource.FromHwnd(hWnd).AddHook(WindowProc);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SendMessage(hWnd, ApiCodes.WM_SYSCOMMAND, new IntPtr(ApiCodes.SC_MINIMIZE), IntPtr.Zero);
}
private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == ApiCodes.WM_SYSCOMMAND)
{
if (wParam.ToInt32() == ApiCodes.SC_MINIMIZE)
{
WindowStyle = WindowStyle.SingleBorderWindow;
WindowState = WindowState.Minimized;
handled = true;
}
else if (wParam.ToInt32() == ApiCodes.SC_RESTORE)
{
WindowState = WindowState.Normal;
WindowStyle = WindowStyle.None;
handled = true;
}
}
return IntPtr.Zero;
}
Neither of the above methods are great, because they are just hacks. The biggest downside is that you can actually see the border reappearing for a moment when you click the button. I'd like to see what others come up with as I don't consider this as a good answer myself.
If you handle the WM_NCCALCSIZE message by returning 0, handle the WM_NCHITTEST message using either your own code (if you want to do manual hit-testing) or also returning 0, and set the WindowStyle to SingleBorder, the window will function like a borderless window but it will have the animations enabled.
If completely necessary, you may also need to handle the WM_GETMINMAXINFO to fix the maximize size - it clips the borders off because the window's style is SingleBorder.
I have found another solution, if you need AllowTransparency = True.
It is not beautiful, rather a bit hacky.
But it is very simple and works great. This uses a empty Window, which is shortly shown when you Minimize/Maximize/Restore your Window, and it has the same position, widht, size and height as your Window. It always has the same Window State as your Window, and it does the animations, which YourWindow lacks because of WindowStyle None and AllowTransparency True. The empty Window has a Window Style SingleBorderWindow and AllowTransparency = false. (by default, so i dont need to set it manually) This is a must or it would not animate. After it has animated, it is completely hidden. You could adjust the look of the Fake Window (BackgroundColor etc...) to YourWindow if it doesnt look good.
public partial Class YourWindowClass : Window
{
Window w;
public YourWindowClass()
{
InitializeComponent();
w = new Window();
w.Width = Width;
w.Height = Height;
w.WindowStartupLocation = this.WindowStartupLocation;
}
Then, you place this in your state changed event:
private void YourWindowClass_StateChanged(object sender, EventArgs e)
{
w.Left = Left;
w.Top = Top;
w.Width = Width;
w.Height = Height;
w.Show();
if (WindowState == WindowState.Minimized)
{
if (w.WindowState == WindowState.Minimized) w.WindowState = WindowState.Normal;
w.WindowState = WindowState.Minimized;
CloseWindow();
}
if (WindowState == WindowState.Normal)
{
w.WindowState = WindowState.Normal;
w.Left = this.Left;
Activate();
CloseWindow();
}
if (WindowState == WindowState.Maximized)
{
w.WindowState = WindowState.Maximized;
Activate();
CloseWindow();
}
}
Finally, create this async Task in YourWindowClass. It will wait shortly and then hide the extra Window.
public async Task CloseWindow()
{
await Task.Delay(600);
w.Visibility = Visibility.Hidden;
}
This will remove the hidden hack Window, so if you close the real Window, the hacky animation Window will close too. Else it wouldnt be Visible to the user because its hidden, but it will still be open and so parts of your App are open. This is a behaviour we dont want, so put this as your Closed Event:
private void YourWindowClass_Closed(object sender, EventArgs e)
{
w.Close();
}
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;
}
I'm using this code to lower window's opacity when user moving it and after drag move is completed i'm incresing window's opacity to it's original value.
private void TopBar_Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
var decreaseOpacityAnim = new DoubleAnimation(0.5, (Duration)TimeSpan.FromSeconds(1));
this.BeginAnimation(UIElement.OpacityProperty, decreaseOpacityAnim);
this.DragMove();
var increaseOpacityAnim = new DoubleAnimation(1, (Duration)TimeSpan.FromSeconds(1));
this.BeginAnimation(UIElement.OpacityProperty, increaseOpacityAnim);
}
}
The problem is when user started to move window and the time window's opacity decreasing, window moves stutter. When animation completed window starts moving without stuttering.
Is there anything i can do to fix this?
I handled MouseMove instead of MouseDown. And secondly, you are using both animations while dragging, hence this stutter. Below code works absolutely fine for me.
<Window ...>
<Grid Background="Yellow" MouseMove="Grid_MouseMove_1"/>
</Window>
Code :
private bool animation_is_running = false;
private void Grid_MouseMove_1(object sender, MouseEventArgs e)
{
if (Mouse.LeftButton == MouseButtonState.Pressed)
{
if (!animation_is_running)
{
var decreaseOpacityAnim = new DoubleAnimation(0.5, (Duration)TimeSpan.FromSeconds(1));
this.BeginAnimation(UIElement.OpacityProperty, decreaseOpacityAnim);
animation_is_running = true;
}
this.DragMove();
}
else
{
if (animation_is_running)
{
var increaseOpacityAnim = new DoubleAnimation(1, (Duration)TimeSpan.FromSeconds(1));
this.BeginAnimation(UIElement.OpacityProperty, increaseOpacityAnim);
animation_is_running = false;
}
}
}
Im new in WPF and Im creating a custom dialog (or a message box) in WPF.
My problem is I cant change everything behind my custom dialog to Opacity=0.5. Only the form who called the custom dialog..
I just want to show my dialog box like in Windows 8.1
Anything would be highly appreciated.
TIA!
Set a property in your MainWindow that controls Opacity of the LayoutRoot. This will change the opacity of your App when the dialog is displayed.
Sample code:
<Grid Name="LayoutRoot" Opacity="{Binding MainWindowOpacity}">
<StackPanel>
<Button Click="Button_Click" Content="Click Me to Show Dialog"/>
<TextBlock Text="WPF" FontSize="72" Margin="50" Foreground="Orange" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
and
public partial class MainWindow : Window
{
public double MainWindowOpacity
{
get { return (double)GetValue(MainWindowOpacityProperty); }
set { SetValue(MainWindowOpacityProperty, value); }
}
// Using a DependencyProperty as the backing store for MainWindowOpacity. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MainWindowOpacityProperty =
DependencyProperty.Register("MainWindowOpacity", typeof(double), typeof(MainWindow), new PropertyMetadata(1.0));
public MainWindow()
{
InitializeComponent();
DataContext = this;
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//if (MainWindowOpacity < 1) MainWindowOpacity = 1.0;
//else MainWindowOpacity = 0.5;
MainWindowOpacity = 0.5;
// show dialog
// boilerplate code from http://stackoverflow.com/questions/6417558/modal-dialog-not-showing-on-top-of-other-windows
Window window = new Window()
{
Title = "WPF Modal Dialog",
ShowInTaskbar = false, // don't show the dialog on the taskbar
Topmost = true, // ensure we're Always On Top
ResizeMode = ResizeMode.NoResize, // remove excess caption bar buttons
Owner = Application.Current.MainWindow,
Width = 300,
Height = 200
};
window.ShowDialog();
MainWindowOpacity = 1.0;
}
}
and the result:
You could add a static method to App.xaml like this:
public partial class App : Application
{
public static void SetWindowsToOpacity(double dOpacity, Window ignoreWindow)
{
foreach (Window win in System.Windows.Application.Current.Windows)
{
if (win != ignoreWindow)
{
win.Opacity = dOpacity;
}
}
}
}
Pass in the Dialog window that you do not want to be set to lower opacity and an opacity value for all the rest.
Call the method like this:
private void button_Click(object sender, RoutedEventArgs e)
{
Window1 dialog = new Window1();
App.SetWindowsToOpacity(0.5, dialog);
dialog.ShowDialog();
App.SetWindowsToOpacity(1.0, null);
}
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.
I'm new to WPF.
I have like 15 grids on my Window and I have a small menu on which I can click and choose which grid to show up or hide. One grid at a time only. I would like that grid to hode (fade out) when I hit Esc. I have all the animations already, I just need to know what grid is visible (active) at the moment.
I don't know how to get current topmost control of my Window.
My solution is when KeyDown event is triggered on my Window to:
private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Escape)
{
//check all grids for IsVisible and on the one that is true make
BeginStoryboard((Storyboard)this.FindResource("theVisibleOne_Hide"));
}
}
By active, I assume that means the one that has keyboard focus. If so, the following will return the control that currently has keyboard input focus:
System.Windows.Input.Keyboard.FocusedElement
You could use it like this:
if (e.Key == System.Windows.Input.Key.Escape)
{
//check all grids for IsVisible and on the one that is true make
var selected = Keyboard.FocusedElement as Grid;
if (selected == null) return;
selected.BeginStoryboard((Storyboard)this.FindResource("HideGrid"));
}
An approach that would be more decoupled would be to create a static attached dependency property. It could be used like this (untested):
<Grid local:Extensions.HideOnEscape="True" .... />
A very rough implementation would look like:
public class Extensions
{
public static readonly DependencyProperty HideOnEscapeProperty =
DependencyProperty.RegisterAttached(
"HideOnEscape",
typeof(bool),
typeof(Extensions),
new UIPropertyMetadata(false, HideOnExtensions_Set));
public static void SetHideOnEscape(DependencyObject obj, bool value)
{
obj.SetValue(HideOnEscapeProperty, value);
}
public static bool GetHideOnEscape(DependencyObject obj)
{
return (bool)obj.GetValue(HideOnEscapeProperty);
}
private static void HideOnExtensions_Set(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as Grid;
if (grid != null)
{
grid.KeyUp += Grid_KeyUp;
}
}
private static void Grid_KeyUp(object sender, KeyEventArgs e)
{
// Check for escape key...
var grid = sender as Grid;
// Build animation in code, or assume a resource exists (grid.FindResource())
// Apply animation to grid
}
}
This would remove the need to have code in codebehind.