9
votes

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

4 Answers

8
votes

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.

16
votes

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.

1
votes

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.

0
votes

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