46 votes

Comment ajouter un bouton supplémentaire à la barre de titre de la fenêtre ?

J'ai vu que certaines applications (peut-être pas les applications .NET) ont un bouton supplémentaire à gauche du bouton de réduction sur la barre de titre du formulaire ? Comment puis-je réaliser cela en C# ?

2 votes

Notez que vous ne devriez pas faire cela : msdn.microsoft.com/fr/us/library/aa974173(v=MSDN.10).aspx - "N'ajoutez pas de contrôles au cadre d'une fenêtre. Placez plutôt les contrôles à l'intérieur de la fenêtre."

3 votes

@Johannes, j'admets que parfois les apps qui font cela peuvent être ennuyeuses mais certaines apps le font très bien. Comme Ultramon qui met un bouton pour changer de moniteur sur le cadre qui semble très natif dans Windows 7. Il y a donc des applications qui le font avec goût.

3 votes

@Josh : Office 2007 l'a également fait (mais ce n'est pas la première fois que l'équipe d'Office ignore les directives Windows UX). Pourtant, ce document est écrit par des professionnels de l'UX et de simples développeurs (ou singes de code) travaillent dur pour les ignorer. Et pour le bien des utilisateurs, je pense qu'ils devraient se contenter de suivre le mouvement, à moins qu'ils ne puissent faire appel à des personnes ayant de l'expérience, des connaissances et, surtout, des tests d'utilisabilité, pour prouver le contraire.

45voto

Chris Taylor Points 25865

UPDATE : Ajout d'une solution qui fonctionnera avec Aero activé pour Windows Vista et Windows 7


***Non-Aero Solution***

La zone non cliente d'une interaction de fenêtre est gérée par une série de messages spécifiques aux non-clients. Par exemple, le message WM_NCPAINT est envoyé à la procédure de la fenêtre pour peindre la zone non cliente.

Je n'ai jamais fait cela à partir de .NET, mais je pense que vous pouvez ignorer le WndProc et traiter les messages WM_NC* pour obtenir ce que vous voulez.

Mise à jour : Comme je n'ai jamais essayé de le faire à partir de .NET, j'ai eu quelques minutes et j'ai pensé faire un essai rapide.

En essayant cela sur Windows 7, j'ai découvert que je devais désactiver les thèmes de la fenêtre si je voulais que l'OS fasse le rendu de base de la zone non cliente. Voici donc un petit test. J'ai utilisé GetWindowDC pour obtenir le DC de la fenêtre entière plutôt que GetDCEx, c'était juste parce que je pouvais interopérer cela depuis la mémoire et que je n'avais pas à chercher toutes les constantes de drapeaux pour GetDcEx. Et bien sûr, le code pourrait bénéficier de plus de contrôles d'erreurs.

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));
            }
            ReleaseDC(m.HWnd, hdc);
          }
          break;
      }
    }
  }
}

Au fait, j'ai appelé DisableProcessWindowsGhosting Ceci empêchera le système d'exploitation de dessiner la zone non cliente si l'application prend trop de temps pour répondre aux messages de Windows. Si vous ne faites pas cela, dans certaines situations, la bordure sera rendue mais vos ornements ne seront pas affichés. Cela dépend donc de vos besoins si cela vous convient ou non.


***Solution supportée par Aéro***

Suite au commentaire de @TheCodeKing, je me suis dit que j'allais y jeter un nouveau coup d'œil. Il s'avère que cela peut être fait d'une manière entièrement documentée tout en supportant Aero. Mais ce n'est pas pour les âmes sensibles. Je ne fournirai pas une solution complète ici, il y a encore quelques problèmes à résoudre, mais cela fait l'essentiel.

Ce code/solution est basé sur l'exemple Win32 qui se trouve à l'emplacement suivant http://msdn.microsoft.com/en-us/library/bb688195(VS.85).aspx

En principe, ce que vous devez faire est le suivant.

  • Étendre la zone client de la fenêtre pour couvrir le cadre. Pour ce faire, il faut traiter le message WM_NCCALCSIZE et renvoyer 0. La zone non cliente a donc une taille de 0 et la zone cliente couvre désormais la totalité de la fenêtre.
  • Prolonger le cadre dans l'espace client en utilisant DwmExtendFrameIntoClientArea . Cela permet au système d'exploitation de rendre le cadre sur la zone client.

Les étapes ci-dessus vous donneront une fenêtre avec le cadre de verre standard, à l'exception du menu système (icône de la fenêtre) et du titre. Les boutons minimiser, maximiser et fermer seront toujours dessinés et fonctionneront. Ce que vous ne pourrez pas faire, c'est faire glisser ou redimensionner la fenêtre, car le cadre n'est pas vraiment là, rappelez-vous que la zone client couvre toute la fenêtre, nous avons simplement demandé au système d'exploitation de dessiner le cadre sur la zone client.

Vous pouvez maintenant dessiner sur la fenêtre comme d'habitude, même sur le dessus du cadre. Vous pouvez même placer des contrôles dans la zone de légende.

Enfin, laissez le DWM s'occuper de la vérification des résultats pour vous, en appelant DwmDefWindowProc de votre WndProc (avant que vous ne l'ayez traité). Il renvoie un booléen indiquant si le DWM a traité le message pour vous.

0 votes

Cette technique ne fonctionne malheureusement pas avec le verre aérodynamique. Je serais intéressé si quelqu'un connaissait le voodoo nécessaire pour faire fonctionner cette technique sur du verre. Jusqu'à présent, je n'ai pas trouvé d'exemples fonctionnels.

1 votes

@TheCodeKing, j'ai mis à jour la réponse pour couvrir votre question. J'ai également réalisé un prototype dans .NET, juste pour confirmer que cela peut fonctionner. Voici l'article MSDN que j'ai référencé msdn.microsoft.com/fr/us/library/bb688195(VS.85).aspx

0 votes

En fait, cela me rappelle que je sais comment faire, j'ai du code quelque part, c'est juste que le rendu en mode conception n'était pas le même qu'en mode exécution. Avez-vous un exemple en C# de la technique que vous avez décrite, j'ai essayé la plupart et ils ne fonctionnent pas ?

17voto

dthorpe Points 23314

Essayez la bibliothèque ActiveButtons : http://www.thecodeking.co.uk/2007/09/adding-caption-buttons-to-non-client.html Il semble qu'il s'agisse d'un téléchargement gratuit avec source.

6voto

hrh Points 302

Une solution simple :

Étape 1 : Créer un formulaire Windows (qui sera votre barre de titre personnalisée)

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

Étape 2. Dans la partie dans laquelle vous voulez utiliser ce contrôle personnalisé, ajoutez

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

Dans votre constructeur... vous pouvez modifier les décalages, mais celui-ci est bien placé pour mon application.

Étape 3. Ajoutez l'événement de déplacement à votre formulaire principal

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

Veuillez me faire savoir si vous souhaitez une meilleure explication de l'un des codes ci-dessus.

0 votes

Qu'est-ce qu'un TitleBarButtons ? Je ne semble pas trouver de références de cette classe.

0 votes

Je suis désolé, j'ai réalisé que je n'étais pas clair. Je crois que "TitleBarButtons" était le formulaire personnalisé créé à l'étape 1. Je vais réviser ma réponse pour la rendre plus claire.

0 votes

@hrh pretty old thread though is it possible in wpf too, adding form to wpf window

4voto

Axarydax Points 7787

Je pense qu'une façon de faire serait de traiter le message WM_NCPAINT (peinture non client) pour dessiner le bouton, et de traiter les clics de souris non client pour savoir que quelqu'un a cliqué sur le "bouton".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X