Randloses WPF-Fenster mit Schatten im VS2012-Stil

 C Programming >> C-Programmierung >  >> Tags >> WPF
Randloses WPF-Fenster mit Schatten im VS2012-Stil

Aktualisierung (Oktober '17)

Es ist jetzt vier Jahre her und ich war daran interessiert, dies wieder anzugehen, und so habe ich mit MahApps.Metro herumgespielt wieder und habe daraus meine eigene Bibliothek abgeleitet. Mein ModernChrome -Bibliothek stellt ein benutzerdefiniertes Fenster bereit, das wie Visual Studio 2017 aussieht:

Da Sie höchstwahrscheinlich nur der Teil mit dem leuchtenden Rand interessiert, sollten Sie entweder MahApps.Metro verwenden selbst oder sehen Sie, wie ich eine Klasse GlowWindowBehavior erstellt habe die leuchtende Ränder an meine benutzerdefinierte ModernWindow anfügt Klasse. Es ist stark von einigen Interna von MahApps.Metro abhängig und die beiden Abhängigkeitseigenschaften GlowBrush und NonActiveGlowBrush .

Wenn Sie nur die leuchtenden Rahmen in Ihre benutzerdefinierten Anwendungen einfügen möchten, verweisen Sie einfach auf MahApps.Metro und übertrage meine GlowWindowBehavior.cs und erstellen Sie eine benutzerdefinierte Fensterklasse und passen Sie die Referenzen entsprechend an. Dies ist eine Angelegenheit von höchstens 15 Minuten.

Auf diese Frage und meine Antwort wurde sehr häufig zugegriffen, daher hoffe ich, dass Sie meine neueste richtige Lösung nützlich finden :)

Ursprünglicher Beitrag (Februar '13)

Ich habe an einer solchen Bibliothek gearbeitet, um die Benutzeroberfläche von Visual Studio 2012 zu kopieren. Ein benutzerdefinierter Chrom ist nicht so schwierig, aber worauf Sie achten sollten, ist dieser leuchtende Rand, der schwer zu implementieren ist. Sie könnten einfach sagen, stellen Sie die Hintergrundfarbe Ihres Fensters auf transparent ein und stellen Sie die Füllung des Hauptrasters auf etwa 30 Pixel ein. Ein Rahmen um das Raster könnte farbig und mit einem farbigen Schatteneffekt verbunden werden, aber dieser Ansatz zwingt Sie dazu, AllowsTransparency einzustellen zu true, was die visuelle Leistung Ihrer Anwendung drastisch reduziert, und das ist etwas, was Sie definitiv nicht tun möchten!

Mein derzeitiger Ansatz, ein solches Fenster zu erstellen, das nur einen farbigen Schatteneffekt an einem Rand hat und transparent ist, aber überhaupt keinen Inhalt hat. Immer wenn sich die Position meines Hauptfensters ändert, aktualisiere ich nur die Position des Fensters, das den Rand enthält. Am Ende handhabe ich also zwei Fenster mit Nachrichten, um vorzutäuschen, dass die Grenze Teil des Hauptfensters wäre. Dies war notwendig, weil die DWM-Bibliothek keine Möglichkeit bietet, einen farbigen Schlagschatteneffekt für Windows zu haben, und ich denke, Visual Studio 2012 macht das ähnlich, wie ich es versucht habe.

Und um diesen Beitrag mit weiteren Informationen zu erweitern:Office 2013 macht das anders. Der Rahmen um ein Fenster ist nur 1 Pixel dick und farbig, aber der Schatten wird von DWM mit einem Code wie diesem hier gezeichnet. Wenn Sie ohne blaue/violette/grüne Ränder leben können und nur die üblichen, ist dies der Ansatz, den ich wählen würde! Stellen Sie einfach AllowsTransparency nicht ein zu wahr, sonst hast du verloren.

Und hier ist ein Screenshot meines Fensters mit seltsamer Farbe, um hervorzuheben, wie es aussieht:

Hier sind einige Tipps, wie Sie beginnen können

Bitte bedenken Sie, dass mein Code ziemlich lang ist, so dass ich Ihnen nur die grundlegenden Dinge zeigen kann und Sie zumindest irgendwie anfangen sollten. Zunächst einmal gehe ich davon aus, dass wir unser Hauptfenster irgendwie gestaltet haben (entweder manuell oder mit dem MahApps.Metro Paket, das ich gestern ausprobiert habe - mit einigen Änderungen am Quellcode ist es wirklich gut (1) ) und wir arbeiten derzeit daran, den leuchtenden Schattenrand zu implementieren, den ich GlowWindow nennen werde von jetzt an. Der einfachste Ansatz besteht darin, ein Fenster mit dem folgenden XAML-Code zu erstellen

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

Das resultierende Fenster sollte wie im folgenden Bild aussehen.

Die nächsten Schritte sind ziemlich schwierig - wenn unser Hauptfenster erscheint, möchten wir das GlowWindow sichtbar machen, aber hinter dem Hauptfenster, und wir müssen die Position des GlowWindow aktualisieren, wenn das Hauptfenster verschoben oder in der Größe geändert wird. Was ich vorschlage, um visuelle Störungen zu vermeiden, die auftreten UND auftreten können, ist, das GlowWindow bei jeder Änderung der Position oder Größe des Fensters auszublenden. Wenn Sie mit einer solchen Aktion fertig sind, zeigen Sie sie einfach erneut.

Ich habe eine Methode, die in verschiedenen Situationen aufgerufen wird (es könnte viel sein, aber nur um sicherzugehen)

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) {
    }
}

Diese Methode wird hauptsächlich in meinem benutzerdefinierten WndProc aufgerufen Ich habe an das Hauptfenster angehängt:

/// <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
}

Es bleibt jedoch noch ein Problem - sobald Sie die Größe Ihres Hauptfensters ändern, kann das GlowWindow nicht das gesamte Fenster mit seiner Größe abdecken. Das heißt, wenn Sie die Größe Ihres Hauptfensters auf etwa MaxWidth Ihres Bildschirms ändern, wäre die Breite des GlowWindow der gleiche Wert + 20, da ich einen Rand von 10 hinzugefügt habe. Daher würde der rechte Rand direkt vor dem rechten Rand des Hauptfensters unterbrochen, was hässlich aussieht. Um dies zu verhindern, habe ich einen Hook verwendet, um das GlowWindow zu einem Werkzeugfenster zu machen:

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

Und dennoch werden wir einige Probleme haben - wenn Sie mit der Maus über das GlowWindow gehen und mit der linken Maustaste klicken, wird es aktiviert und erhält den Fokus, was bedeutet, dass es das Hauptfenster überlappt, was so aussieht:

Um das zu verhindern, fangen Sie einfach die Activated Ereignis der Grenze und bringt das Hauptfenster in den Vordergrund.

Wie sollten SIE das tun?

Ich schlage vor, dies NICHT auszuprobieren - es hat ungefähr einen Monat gedauert, bis ich das erreicht hatte, was ich wollte, und es hat immer noch einige Probleme, so dass ich mich für einen Ansatz wie Office 2013 entscheiden würde - farbiger Rand und üblicher Schatten mit den DWM-API-Aufrufen - sonst nichts und sieht trotzdem gut aus.

(1) Ich habe gerade einige Dateien bearbeitet, um den Rahmen um das Fenster zu aktivieren, der in Windows 8 für mich deaktiviert ist. Außerdem habe ich den Padding manipuliert der Titelleiste so, dass sie nicht so gestaucht aussieht, und schließlich habe ich die All-Caps-Eigenschaft geändert, um die Art und Weise des Renderns des Titels in Visual Studio nachzuahmen. Bisher die MahApps.Metro ist eine bessere Möglichkeit, das Hauptfenster zu zeichnen, da es sogar AeroSnap unterstützt, was ich mit normalen P/Invoke-Aufrufen nicht implementieren könnte.


Sie können diesen einfachen XAML-Code verwenden

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

Dies wird als "Metro-Stil" (Windows 8-Stil) bezeichnet. Ich denke, dass dieser Code Project-Artikel für Sie interessant ist und Ihnen helfen wird.

Sie können auch Elysium, das unter der MIT-Lizenz lizenziert ist und die Klassen ApplicationBar und ToastNotification enthält, oder MetroToolKit von Codeplext ausprobieren.

Dies ist ein großartiges Tutorial über Elysium, ich denke, es hilft Ihnen.

Fügen Sie für Schatten einfach einen BitmapEffect hinzu zu einem Border von Ihrem Grid in XAML:

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