具有最小化动画的自定义窗口样式

Posted

技术标签:

【中文标题】具有最小化动画的自定义窗口样式【英文标题】:Custom window style with minimize animation 【发布时间】:2014-02-20 11:50:24 【问题描述】:

我想要一个自定义的窗口,所以遵循了一些教程,通过将窗口样式设置为无,然后自己添加标题栏/恢复/最小化/关闭按钮来实现这一点。最小化是通过简单处理点击事件并将Window-state设置为最小化来实现的,但这并没有显示你在Windows 7上看到的最小化动画,只是瞬间隐藏了窗口,与其他窗口一起使用时感觉很奇怪做动画,因​​为您倾向于感觉应用程序正在关闭。

那么,是否有启用该动画的方法? .. 当您将 WindowStyle 更改为 none 时,它​​似乎被禁用了。

编辑:测试代码

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

【问题讨论】:

【参考方案1】:

.NET 的一个新特性解决了这个问题。 留下你的 WindowStyle="SingleBorder" 或 "ThreeDBorder" 离开 ResizeMode="CanResize"

然后把这个添加到xaml里面

<Window>
  <WindowChrome.WindowChrome>
    <WindowChrome GlassFrameThickness="0" CornerRadius="0" CaptionHeight="0" UseAeroCaptionButtons="False" ResizeBorderThickness="7"/>
  </WindowChrome.WindowChrome>
</Window>

窗口将没有任何默认边框,但仍允许调整大小并且在最大化时不会覆盖任务栏。它还会像以前一样显示最小化动画。

编辑

不幸的是,当使用 WindowStyle="None" 时,它仍然会禁用动画并覆盖任务栏。因此,如果您尝试制作透明窗口,则此方法不起作用。

【讨论】:

如果您将 AllowsTransparency 设置为 true 并且 WindowStyle = none,则最小化动画将消失。 @TheMuffinMan 我似乎遇到了和你一样的问题,需要一个透明的窗口,但是,甜美的动画。你找到其他方法了吗? 这根本没有回答问题! @FrankZ。这将创建一个无边框窗口,在最小化时仍显示动画。我错过了问题的另一部分吗? @FrankZ。您对 WindowStyle=None 是正确的。调整大小仍然有效,但动画确实中断了。我已经更正了我的答案。【参考方案2】:

在尝试了一下之后修改了答案。

有两种选择: 1. 您可以在最小化和激活窗口之前更改样式:

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

此解决方案有一个限制 - 如果您从任务栏最小化它,它不会为窗口设置动画。

2. 通过向窗口发送带有 SC_MINIMIZE 参数的 WM_SYSCOMMAND 消息并通过挂钩消息 (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;

上述方法都不是很好,因为它们只是黑客。最大的缺点是,当您单击按钮时,您实际上可以看到边框再次出现。我想看看其他人的想法,因为我自己认为这不是一个好的答案。

【讨论】:

谢谢。我能够获得第二种工作方法,但不是第一种。第一个仍然没有显示任何动画。我用我使用的代码编辑了我的帖子 我已经更新了我的答案。我错了。在这两种情况下,当您单击按钮时,您都会注意到标题重新出现。但是当您自己发送消息时,窗口在最小化时始终是动画的。如果你只是改变按钮点击的边框,当你通过任务栏最小化它时它不会动画。 很抱歉这么晚才回来,我想这已经足够了。除非你有非常复杂的内容和延迟渲染,否则它并不是那么明显 在你的第一个黑客作品中,但不幸的后果是在恢复窗口时导致窗口大小错误。 我很好奇为什么没有人知道 windows 是如何正常处理这个问题的,所以我们可以修改源代码并修改它以适应我们的情况。【参考方案3】:

如果您通过返回 0 来处理 WM_NCCALCSIZE 消息,则使用您自己的代码(如果您想进行手动命中测试)或返回 0 来处理 WM_NCHITTEST 消息,并将 WindowStyle 设置为 SingleBorder,即窗口将像无边框窗口一样运行,但会启用动画。

如果完全有必要,您可能还需要处理 WM_GETMINMAXINFO 来修复最大化大小 - 因为窗口的样式是 SingleBorder,它会剪掉边框。

【讨论】:

【参考方案4】:

如果您需要 AllowTransparency = True,我已经找到了另一种解决方案。 它并不漂亮,而是有点hacky。 但它非常简单并且效果很好。这使用了一个空窗口,当您最小化/最大化/恢复您的窗口时会立即显示它,它与您的窗口具有相同的位置、宽度、大小和高度。它始终具有与您的 Window 相同的 Window State,并且它会执行 YourWindow 由于 WindowStyle None 和 AllowTransparency True 而缺少的动画。空窗口具有窗口样式 SingleBorderWindow 和 AllowTransparency = false。 (默认情况下,所以我不需要手动设置)这是必须的,否则它不会动画。动画完成后,它是完全隐藏的。如果看起来不好,您可以将假窗口(背景颜色等)的外观调整为 YourWindow。

public partial Class YourWindowClass : Window


    Window w;
    public YourWindowClass()
    
        InitializeComponent();
        w = new Window();
        w.Width = Width;
        w.Height = Height;
        w.WindowStartupLocation = this.WindowStartupLocation;           
    

然后,你把它放在你的状态改变事件中:

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

最后,在 YourWindowClass 中创建这个异步任务。它会稍等片刻,然后隐藏多余的窗口。

    public async Task CloseWindow()
    
        await Task.Delay(600);
        w.Visibility = Visibility.Hidden;
    

这会移除隐藏的hack Window,所以如果你关闭真正的Window,hacky 动画Window 也会关闭。否则它对用户来说是不可见的,因为它是隐藏的,但它仍然是打开的,所以你的应用程序的一部分是打开的。这是我们不想要的行为,所以把它作为你的 Closed Event:

    private void YourWindowClass_Closed(object sender, EventArgs e)
    
        w.Close();
    

【讨论】:

这根本不起作用。主窗口显示在假窗口完成动画之前。

以上是关于具有最小化动画的自定义窗口样式的主要内容,如果未能解决你的问题,请参考以下文章

动画边界更改时具有 CALayer 有线效果的自定义视图

来自 XIB 的自定义 UIView 加载具有巨大规模的子视图

Electron 应用程序窗口在最小化、最大化和关闭事件时仍然有动画

如何在带有标题视图和 UITableview 的自定义视图中正确设置 UISearchbar 的动画?

具有单独移动背景的自定义 segue

Django 在自定义 404 页面上传输具有 MIME 类型 text/html 的样式表