簡體   English   中英

帶陰影的 WPF 無邊框窗口 VS2012 樣式

[英]WPF borderless window with shadow VS2012 style

我正在嘗試創建一個看起來像 Visual Studio 2012 的應用程序。我使用WindowChrome刪除了窗口邊框,並在我的 xaml 中更改了邊框顏色。

我不知道怎么做的是繪制窗口的陰影,在這里你可以看到我所說的截圖:

帶有陰影的 Visual Studio 無邊框窗口

如您所見,有一個陰影,它的顏色也是邊框顏色

你知道如何使用 WPF 實現它嗎?

更新(2017 年 10 月)

現在已經四年了,我對再次解決這個問題很感興趣,因此我又一次搞砸了MahApps.Metro基於它派生了我自己的庫 我的ModernChrome庫提供了一個類似於 Visual Studio 2017 的自定義窗口:

ModernChrome 示例

由於您很可能只對有關發光邊框的部分感興趣,因此您應該使用MahApps.Metro本身或查看我如何創建一個GlowWindowBehavior類, GlowWindowBehavior發光邊框附加到我的自定義ModernWindow類。 它嚴重依賴於 MahApps.Metro 的一些內部結構和兩個依賴屬性GlowBrushNonActiveGlowBrush

如果您只想在自定義應用程序中包含發光邊框,只需引用MahApps.Metro並復制我的GlowWindowBehavior.cs並創建自定義窗口類並相應地調整引用。 這最多是 15 分鍾的問題。

這個問題和我的回答經常被訪問,所以我希望你會發現我最新的正確解決方案有用:)


原帖(2013 年 2 月)

我一直在研究這樣一個庫來復制 Visual Studio 2012 用戶界面。 自定義 chrome 並不難,但您應該注意的是這個難以實現的發光邊框。 您可以說將窗口的背景顏色設置為透明,並將主網格的填充設置為大約 30 像素。 網格周圍的邊框可以着色並與彩色陰影效果相關聯,但這種方法會強制您將AllowsTransparency設置為 true,這會大大降低應用程序的視覺性能,這是您絕對不想做的事情!

我目前創建這樣一個窗口的方法是在邊框上具有彩色陰影效果並且是透明的但根本沒有內容。 每當我的主窗口的位置發生變化時,我只更新包含邊框的窗口的位置。 所以最后我正在處理兩個帶有消息的窗口,以假裝邊框是主窗口的一部分。 這是必要的,因為 DWM 庫沒有提供一種為 Windows 提供彩色陰影效果的方法,我認為 Visual Studio 2012 與我嘗試過的類似。

並用更多信息擴展這篇文章:Office 2013 的做法有所不同。 圍繞窗口的邊框僅僅是1px的厚,着色,但陰影是由DWM與像一個代碼繪制的這一個在這里。 如果您可以在沒有藍色/紫色/綠色邊框和普通邊框的情況下生活,這就是我會選擇的方法! 只是不要將AllowsTransparency設置為 true,否則你就輸了。

這是我的窗口的屏幕截圖,用奇怪的顏色突出顯示它的樣子:

地鐵界面


這里有一些關於如何開始的提示

請記住,我的代碼很長,因此我只能向您展示要做的基本事情,而您至少應該能夠以某種方式開始。 首先,我將假設我們以某種方式設計了我們的主窗口(手動或使用我昨天嘗試的MahApps.Metro包 - 對源代碼進行了一些修改,這真的很好(1) ),我們目前正在努力實現發光陰影邊框,從現在開始我將稱之為GlowWindow 最簡單的方法是使用以下 XAML 代碼創建一個窗口

<Window x:Class="MetroUI.Views.GlowWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="GlowWindow"
    Title="" Width="300" Height="100" WindowStartupLocation="Manual"
    AllowsTransparency="True" Background="Transparent" WindowStyle="None"
    ShowInTaskbar="False" Foreground="#007acc" MaxWidth="5000" MaxHeight="5000">
    <Border x:Name="OuterGlow" Margin="10" Background="Transparent"
            BorderBrush="{Binding Foreground, ElementName=GlowWindow}"
            BorderThickness="5">
        <Border.Effect>
            <BlurEffect KernelType="Gaussian" Radius="15" RenderingBias="Quality" />
        </Border.Effect>
    </Border>
</Window>

生成的窗口應如下圖所示。

輝光窗

接下來的步驟非常困難 - 當我們的主窗口產生時,我們想讓 GlowWindow 可見但在主窗口后面,我們必須在主窗口移動或調整大小時更新 GlowWindow 的位置。 我建議防止可能和將發生的視覺故障是在窗口位置或大小的每次更改期間隱藏 GlowWindow。 完成此類操作后,只需再次顯示即可。

我有一些在不同情況下調用的方法(可能很多,但只是為了確定)

private void UpdateGlowWindow(bool isActivated = false) {
    if(this.DisableComposite || this.IsMaximized) {
        this.glowWindow.Visibility = System.Windows.Visibility.Collapsed;
        return;
    }
    try {
        this.glowWindow.Left = this.Left - 10;
        this.glowWindow.Top = this.Top - 10;
        this.glowWindow.Width = this.Width + 20;
        this.glowWindow.Height = this.Height + 20;
        this.glowWindow.Visibility = System.Windows.Visibility.Visible;
        if(!isActivated)
            this.glowWindow.Activate();
    } catch(Exception) {
    }
}

此方法主要在我附加到主窗口的自定義WndProc調用:

/// <summary>
/// An application-defined function that processes messages sent to a window. The WNDPROC type
/// defines a pointer to this callback function.
/// </summary>
/// <param name="hwnd">A handle to the window.</param>
/// <param name="uMsg">The message.</param>
/// <param name="wParam">Additional message information. The contents of this parameter depend on
/// the value of the uMsg parameter.</param>
/// <param name="lParam">Additional message information. The contents of this parameter depend on
/// the value of the uMsg parameter.</param>
/// <param name="handled">Reference to boolean value which indicates whether a message was handled.
/// </param>
/// <returns>The return value is the result of the message processing and depends on the message sent.
/// </returns>
private IntPtr WindowProc(IntPtr hwnd, int uMsg, IntPtr wParam, IntPtr lParam, ref bool handled) {
    // BEGIN UNMANAGED WIN32
    switch((WinRT.Message)uMsg) {
        case WinRT.Message.WM_SIZE:
            switch((WinRT.Size)wParam) {
                case WinRT.Size.SIZE_MAXIMIZED:
                    this.Left = this.Top = 0;
                    if(!this.IsMaximized)
                        this.IsMaximized = true;
                    this.UpdateChrome();
                    break;
                case WinRT.Size.SIZE_RESTORED:
                    if(this.IsMaximized)
                        this.IsMaximized = false;
                    this.UpdateChrome();
                    break;
            }
            break;

        case WinRT.Message.WM_WINDOWPOSCHANGING:
            WinRT.WINDOWPOS windowPosition = (WinRT.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WinRT.WINDOWPOS));
            Window handledWindow = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
            if(handledWindow == null)
                return IntPtr.Zero;
            bool hasChangedPosition = false;
            if(this.IsMaximized == true && (this.Left != 0 || this.Top != 0)) {
                windowPosition.x = windowPosition.y = 0;
                windowPosition.cx = (int)SystemParameters.WorkArea.Width;
                windowPosition.cy = (int)SystemParameters.WorkArea.Height;
                hasChangedPosition = true;
                this.UpdateChrome();
                this.UpdateGlowWindow();
            }
            if(!hasChangedPosition)
                return IntPtr.Zero;
            Marshal.StructureToPtr(windowPosition, lParam, true);
            handled = true;
            break;
    }
    return IntPtr.Zero;
    // END UNMANAGED WIN32
}

然而,仍然存在一個問題——一旦您調整主窗口的大小,GlowWindow 將無法以其大小覆蓋整個窗口。 也就是說,如果您將主窗口的大小調整為屏幕的 MaxWidth 左右,那么 GlowWindow 的寬度將是相同的值 + 20,因為我為其添加了 10 的邊距。 因此,右邊緣會在看起來難看的主窗口的右邊緣之前被中斷。 為了防止這種情況,我使用了一個鈎子來使 GlowWindow 成為一個工具窗口:

this.Loaded += delegate {
    WindowInteropHelper wndHelper = new WindowInteropHelper(this);
    int exStyle = (int)WinRT.GetWindowLong(wndHelper.Handle, (int)WinRT.GetWindowLongFields.GWL_EXSTYLE);
    exStyle |= (int)WinRT.ExtendedWindowStyles.WS_EX_TOOLWINDOW;
    WinRT.SetWindowLong(wndHelper.Handle, (int)WinRT.GetWindowLongFields.GWL_EXSTYLE, (IntPtr)exStyle);
};

我們仍然會遇到一些問題 - 當您將鼠標移到 GlowWindow 上並左鍵單擊時,它將被激活並獲得焦點,這意味着它將與主窗口重疊,如下所示:

重疊發光窗口

為了防止這種情況,只需捕獲邊框的Activated事件並將主窗口置於前台。

你應該怎么做?

我建議不要嘗試這個 - 我花了大約一個月的時間來實現我想要的,但它仍然存在一些問題,所以我會采用像 Office 2013 那樣的方法 - 彩色邊框和帶有 DWM API 調用的通常陰影 -沒有別的,它看起來還是不錯的。

辦公室 2013


(1)我剛剛編輯了一些文件,以啟用在 Window 8 上為我禁用的窗口周圍的邊框。 此外,我操縱了標題欄的Padding ,使其看起來不像原地那樣被擠壓,最后我更改了 All-Caps 屬性以模仿 Visual Studio 呈現標題的方式。 到目前為止, MahApps.Metro是繪制主窗口的更好方法,因為它甚至支持 AeroSnap,我無法用通常的 P/Invoke 調用來實現。

您可以使用這個簡單的 xaml 代碼

<Window x:Class="VS2012.MainWindow" 
         xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation 
         xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml 
         Title="MainWindow" 
         Height="100" Width="200" 
         AllowsTransparency="True" WindowStyle="None" Background="Transparent"> 
<Border BorderBrush="DarkOrange" BorderThickness="1" Background="White" Margin="5">
         <Border.Effect>
                <DropShadowEffect ShadowDepth="0" BlurRadius="5" Color="DarkOrange"/>
         </Border.Effect>
</Border>
</Window> 

這稱為“Metro 風格”(Windows 8 風格)。 我認為這篇Code Project 文章對你很有趣,它會對你有所幫助。

您可以嘗試Elysium ,它在 MIT 許可下獲得許可並包含 ApplicationBar 和 ToastNotification 類,或來自 codeplext 的MetroToolKit

是一個關於 Elysium 的很棒的教程,我認為它對你有幫助。

對於陰影,只需將BitmapEffect到 XAML Grid中的Border

<Grid>
    <Border BorderBrush="#FF006900" BorderThickness="3" Height="157" HorizontalAlignment="Left" Margin="12,12,0,0" Name="border1" VerticalAlignment="Top" Width="479" Background="#FFCEFFE1" CornerRadius="20, 20, 20, 20">
        <Border.BitmapEffect>
          <DropShadowBitmapEffect Color="Black" Direction="320" ShadowDepth="10" Opacity="0.5" Softness="5" />
        </Border.BitmapEffect>
        <TextBlock Height="179" Name="textBlock1" Text="Hello, this is a beautiful DropShadow WPF Window Example." FontSize="40" TextWrapping="Wrap" TextAlignment="Center" Foreground="#FF245829" />
    </Border>
</Grid>

在此處輸入圖片說明

<Window x:Class="MyProject.MiniWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:MyProject"
    mc:Ignorable="d" 
    WindowStyle="None" 
    Title="MiniWindow" Background="Transparent"
    Height="200" Width="200" 
    >

<WindowChrome.WindowChrome>
    <WindowChrome 
    CaptionHeight="0"
    ResizeBorderThickness="4" />
</WindowChrome.WindowChrome>

<Grid Margin="0">
    <Border BorderThickness="3">
        <Border BorderThickness="1" Margin="0" BorderBrush="#ff007acc">
            <Border.Effect>
                <DropShadowEffect Color="#ff007acc" Direction="132" ShadowDepth="0" BlurRadius="8" />
            </Border.Effect>
            <Grid   Background="#ff2d2d30">
                
            </Grid>
        </Border>
    </Border>
    
    
</Grid>

我試圖獲得相同的效果,我的應用程序使用 .NET 4,因此我不能直接使用WindowChrome (因此,我使用Microsoft Windows Shell庫來獲得相同的效果)。

在這個線程中正確地指出,使用 spy++ 可以看到 Visual Studio 有四個稱為VisualStudioGlowWindow 的窗口來實現發光效果。 已經在很多地方描述了AllowsTransparency屬性是如何降低性能的。

所以,我嘗試走 VS 的方式,結果還不錯(至少,對我來說); 不需要在主窗口上使用模糊或類似的效果,我只需要與一些窗口狀態(焦點/可見/隱藏)作斗爭。

我已經把所有需要的東西都放在了github 上——希望這能有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM