2010-05-15 6 views
32

Ich habe gesehen, dass einige Anwendungen (vielleicht nicht .NET-Anwendungen) eine zusätzliche Schaltfläche auf der linken Seite von der Minimieren-Schaltfläche in der Titelleiste des Formulars haben? Wie kann ich dies in C# erreichen?Wie fügt man der Titelleiste des Fensters eine zusätzliche Schaltfläche hinzu?

+2

Beachten Sie, dass Sie dies nicht tun sollten: http://msdn.microsoft.com/en-us/library/aa974173(v=MSDN.10).aspx - »Fügen Sie keine Steuerelemente zu einem Fensterrahmen hinzu. Setze stattdessen die Steuerelemente in das Fenster. « – Joey

+3

@Johannes, ich gebe zu, manchmal Apps, die das tun, können nervig sein, aber einige Apps machen das sehr gut. Wie Ultramon, das eine Schaltfläche zum Austauschen von Monitoren auf den Rahmen setzt, die in Windows 7 sehr nativ aussieht. Es gibt also Anwendungen, die es geschmackvoll machen. – Josh

+3

@Josh: Office 2007 hat es auch getan (aber das ist nicht das erste Auftreten des Office-Teams, das Windows-UX-Richtlinien ignoriert). Dennoch wird dieses Dokument von UX-Profis geschrieben und nur Entwickler (oder Code-Affen) arbeiten hart daran, sie zu ignorieren. Und im Interesse der Benutzer sollten sie meiner Meinung nach nur folgen, es sei denn, sie bringen Menschen mit Erfahrung und Wissen und - was am wichtigsten ist - Usability-Tests, um das Gegenteil zu beweisen. – Joey

Antwort

32

UPDATE: eine Lösung hinzugefügt, die mit Aero aktiviert für Windows Vista und Windows 7


Nicht-Aero-Lösung

Der Nicht-Client-Bereich eines Fensters Interaktion arbeiten wird von einer Reihe von nicht kundenspezifischen Nachrichten verwaltet. Zum Beispiel wird eine WM_NCPAINT-Nachricht an die Fensterprozedur gesendet, um den Nicht-Client-Bereich zu zeichnen.

Ich habe das nie von .NET gemacht, aber ich vermute, Sie können die WndProc überlagern und die WM_NC * Nachrichten behandeln, um zu erreichen, was Sie wollen.

Update: Da ich das nie aus .NET versucht habe, habe ich ein paar Minuten und dachte, ich würde es versuchen.

Probieren Sie dies auf Windows 7, ich fand, dass ich die Designs für das Fenster deaktivieren musste, wenn ich OS das Basis-Rendering des Nicht-Client-Bereichs durchführen wollte. Also hier ist ein kurzer Test. Ich habe GetWindowDC verwendet, um den DC des gesamten Fensters anstelle von GetDCEx zu erhalten, das war nur, weil ich das aus dem Speicher interopieren konnte und nicht alle Flag-Konstanten für GetDcEx nachgeschlagen hatte. Und natürlich könnte der Code mit mehr Fehlerprüfung auskommen.

using System; 
using System.Drawing; 
using System.Windows.Forms; 
using System.Runtime.InteropServices; 

namespace WindowsFormsApplication1 
{ 
    public partial class CustomBorderForm : Form 
    { 
    const int WM_NCPAINT = 0x85; 

    [DllImport("user32.dll", SetLastError = true)] 
    public static extern IntPtr GetWindowDC(IntPtr hwnd); 

    [DllImport("user32.dll", SetLastError = true)] 
    public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc); 

    [DllImport("user32.dll", SetLastError = true)] 
    public static extern void DisableProcessWindowsGhosting(); 

    [DllImport("UxTheme.dll", SetLastError = true, CharSet = CharSet.Unicode)] 
    public static extern IntPtr SetWindowTheme(IntPtr hwnd, string pszSubAppName, string pszSubIdList); 

    public CustomBorderForm() 
    { 
     // This could be called from main. 
     DisableProcessWindowsGhosting(); 

     InitializeComponent(); 
    } 

    protected override void OnHandleCreated(EventArgs e) 
    { 
     SetWindowTheme(this.Handle, "", ""); 
     base.OnHandleCreated(e); 
    } 

    protected override void WndProc(ref Message m) 
    { 
     base.WndProc(ref m); 

     switch (m.Msg) 
     { 
     case WM_NCPAINT: 
      { 
      IntPtr hdc = GetWindowDC(m.HWnd); 
      using (Graphics g = Graphics.FromHdc(hdc)) 
      { 
       g.FillEllipse(Brushes.Red, new Rectangle((Width-20)/2, 8, 20, 20)); 
      } 
      int r = ReleaseDC(m.HWnd, hdc); 
      } 
      break; 
     } 
    } 
    } 
} 

Btw. Ich rief DisableProcessWindowsGhosting an, dies wird das OS davon abhalten, den Nicht-Client-Bereich zu zeichnen, wenn die Anwendung zu lange braucht, um auf Windows-Nachrichten zu antworten. Wenn Sie dies nicht tun, wird der Rahmen in einigen Situationen neu gerendert, aber Ihre Verzierungen werden nicht angezeigt. Es hängt also von Ihren Anforderungen ab, ob es für Sie richtig ist oder nicht.


Aero Lösung unterstützt

durch den Kommentar Angeregt von @TheCodeKing, ich dachte, ich würde in diesem anderen Blick nehmen. Es stellt sich heraus, dass dies in vollständig dokumentierter Weise durchgeführt werden kann, während Aero unterstützt wird. Aber es ist nichts für schwache Nerven. Ich werde hier keine komplette Lösung anbieten, es gibt immer noch einige Knicke zum Training, aber es macht die Grundlagen.

Dieser Code/Lösung wird das Win32-Beispiel basiert weg, die an der folgenden Position gefunden werden kann http://msdn.microsoft.com/en-us/library/bb688195(VS.85).aspx

Grundsätzlich, was Sie tun müssen, ist die folgende.

  • Erweitern Sie den Clientbereich des Fensters, um den Frame abzudecken. Dies geschieht, indem die Nachricht WM_NCCALCSIZE verarbeitet und 0 zurückgegeben wird. Dadurch erhält der Nicht-Client-Bereich die Größe 0 und der Clientbereich deckt jetzt das gesamte Fenster ab.
  • Erweitern Sie den Frame mit DwmExtendFrameIntoClientArea in den Clientbereich. Dadurch erhält das Betriebssystem den Frame über den Clientbereich gerendert.
  • Die obigen Schritte geben Ihnen ein Fenster mit dem Standardglasrahmen, das Systemmenü (Fenstersymbol) und den Titel ausgenommen.Die Schaltflächen zum Minimieren, Maximieren und Schließen werden weiterhin gezeichnet und funktionieren. Was Sie nicht tun können, ist das Fenster zu ziehen oder seine Größe zu ändern, weil der Frame nicht wirklich da ist. Denken Sie daran, dass der Clientbereich das gesamte Fenster abdeckt. Wir haben das Betriebssystem gebeten, den Frame auf den Clientbereich zu zeichnen.

    Jetzt können Sie wie gewohnt auf das Fenster zeichnen, sogar auf den Rahmen. Sie können sogar Steuerelemente im Beschriftungsbereich platzieren.

    Der letzte Schritt besteht darin, die WM_NCHITTEST Nachricht zu behandeln, wo Sie die Position des Mauszeigers überprüfen und einen Indikator zurückgeben, der Windows mitteilt, welcher Bereich des Fensters die Maus über ist. Wenn Sie beispielsweise HT_CAPTION zurückgeben, verhält sich das Fenster so, als befände sich die Maus über der Beschriftung und Sie können das Fenster usw. ziehen. Diese Funktion erfordert eine vollständige Implementierung, um ein voll funktionsfähiges Fenster mit einem benutzerdefinierten Rahmen zu erhalten .

    +0

    Diese Technik funktioniert leider nicht mit Aero-Glas. Ich würde mich dafür interessieren, wenn jemand den Voodoo kennt, der benötigt wird, um an Glas zu arbeiten. Bisher habe ich keine Arbeitsbeispiele gefunden. – TheCodeKing

    +0

    @TheCodeKing, Ich habe die Antwort aktualisiert, um Ihre Frage zu decken. Ich habe das auch in .NET prototypisiert, nur um zu bestätigen, dass es zum Funktionieren gebracht werden kann. Hier ist der MSDN Artikel, auf den ich verwiesen habe http://msdn.microsoft.com/en-us/library/bb688195(VS.85).aspx –

    +0

    Eigentlich erinnert mich das, ich weiß, wie man das macht, ich habe irgendwo Code Es hat im Design-Modus einfach nicht dargestellt, wie es zur Laufzeit aussah.Haben Sie ein C# -Beispiel für die Technik, die Sie beschrieben haben, die ich am meisten ausprobiert habe und die nicht funktioniert? – TheCodeKing

    4

    Ich denke, ein Weg, dies zu tun wäre, WM_NCPAINT Nachricht (Nicht-Client-Farbe) zu behandeln, um die Schaltfläche zu zeichnen und nicht-Client-Mausklicks zu wissen, dass jemand auf die Schaltfläche geklickt hat.

    3

    Einfache Lösung:

    Schritt 1: Erstellen Sie ein Windows Form (dies wird Ihre eigenen Titel bar)

    -Set Form Border Style to None 
    -Add whatever controls you would like to this 
    -I will name this custom form "TitleBarButtons" 
    

    Schritt 2. In der von Ihnen, diese benutzerdefinierten Steuerelement in Add verwenden möchten

    titleBarBtn = new TitleBarButtons(); 
    titleBarBtn.Location = new Point(this.Location.X + 100, this.Location.Y+5); 
    titleBarBtn.Show(); 
    titleBarBtn.Owner = this; 
    

    Um Ihren Konstruktor ... Sie können Chaos mit den Offsets in einer schönen Lage passen diese nur für meine App

    Schritt 3. den Umzug Veranstaltung zu Ihrem Hauptformular hinzufügen

    private void Form14_Move(object sender, EventArgs e) 
    { 
        titleBarBtn.Location = new Point(this.Location.X + 100, this.Location.Y+5); 
    } 
    

    Bitte lassen Sie mich wissen, wenn Sie eine bessere Erklärung für eine der oben genannten Code möchten.

    +0

    Was ist ein TitleBarButtons? Ich kann keine Referenzen dieser Klasse finden. – Borik

    +0

    Es tut mir leid ich merkte, ich war nicht klar, ich glaube „TitleBarButtons“ war das benutzerdefinierte Formular im ersten Schritt gemacht. Ich überarbeite meine Antwort, um sie klarer zu machen. – hrh

    +0

    @hrh ziemlich alter thread obwohl es in wpf auch möglich ist, form zu wpf window hinzuzufügen – murmansk

    Verwandte Themen