11

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));
    }
}
pastillman
  • 1,104
  • 2
  • 16
  • 27

4 Answers4

17

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.

bwing
  • 731
  • 8
  • 12
  • 1
    If you have AllowsTransparency set to true and WindowStyle = none the minimize animation is gone. – The Muffin Man Oct 22 '20 at 23:15
  • @TheMuffinMan I seem to face the same problem you had, Needing a Transparent window and yet, the sweet animation. Did you find another way? – Angevil Apr 14 '21 at 21:21
  • This does not answer the question at all! – Frank Z. Jul 29 '21 at 17:08
  • @FrankZ. This creates a borderless window that still shows the animation when minimized. Is there another part of the question I missed? – bwing Jul 29 '21 at 18:01
  • @bwing I apologize, I missed the key property `UseAeroCaptionButtons="False"`. By the way, this doesn't work with `WindowStyle=None` and `AllowsTransparency=True`, so would you care to explain the second part of your answer? – Frank Z. Aug 05 '21 at 03:00
  • 1
    @FrankZ. you are correct regarding the WindowStyle=None. The resizing was still working, but the animation does break. I have corrected my answer. – bwing Aug 05 '21 at 05:56
8

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.

Fayilt
  • 1,042
  • 7
  • 16
  • Thankyou. I was able to get the second approach to work, but not the first. The first still doesn't show any animation. I've edited my post with the code Im using – pastillman Jan 30 '14 at 22:45
  • I've updated my answer. I was slightly wrong. In both cases you will notice the header reappearing when you click the button. But when you send the message yourself, the window is always animated when minimised. If you just change the border on button click, it's not animated when you minimise it through the taskbar. – Fayilt Jan 31 '14 at 13:19
  • Sorry for getting back so late, that's good enough I think. Its not really that noticeable until you have very complex content with laggy rendering – pastillman Feb 04 '14 at 20:26
  • In the Your first hack works, but has the unfortunate consequence of causing the Window size to be wrong when the window is restored. – Scott Solmer Jul 07 '16 at 18:39
  • I'm curious why no one knows how windows is handling this normally so we can modify the source code and modify it to work in our case. – The Muffin Man May 23 '20 at 04:46
2

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.

rookie1024
  • 612
  • 7
  • 18
0

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