62 votes

Est-il possible d'utiliser ShowDialog sans bloquer tous les formulaires?

J'espère pouvoir expliquer cela assez clairement. J'ai mon formulaire principal (A) et il ouvre 1 formulaire enfant (B) en utilisant form.Show () et un deuxième formulaire enfant (C) en utilisant form.Show (). Maintenant, je veux que le formulaire enfant B ouvre un formulaire (D) à l'aide de form.ShowDialog (). Lorsque je fais cela, il bloque également la forme A et la forme C. Existe-t-il un moyen d'ouvrir une boîte de dialogue modale et de la faire bloquer uniquement le formulaire qui l'a ouverte?

Diagramme

112voto

P Daddy Points 14228

À l'aide de plusieurs GUI fils est un travail délicat, et je ne vous le conseille, si c'est votre seule motivation pour le faire.

Un beaucoup plus approprié approche consiste à utiliser des Show() au lieu de ShowDialog(), et de désactiver le propriétaire de forme jusqu'à ce que le popup du retour. Il y a juste quatre considérations:

  1. Lors de l' ShowDialog(owner) est utilisé, le popup forme reste au-dessus de son propriétaire. La même chose est vraie lorsque vous utilisez Show(owner). Alternativement, vous pouvez définir l' Owner de la propriété de manière explicite, avec le même effet.

  2. Si vous définissez le propriétaire du formulaire Enabled de la propriété d' false, le formulaire affiche un état désactivé (contrôles enfants sont "grisées"), alors que lorsqu' ShowDialog est utilisé, le propriétaire de la forme est toujours désactivé, mais ne montre pas un état désactivé.

    Lorsque vous appelez ShowDialog, le propriétaire formulaire est désactivé dans le code Win32-son WS_DISABLED bit de style est défini. Cela provoque la perte de la capacité de gain de l'accent et de "ding" quand on clique dessus, mais ne permet pas de dessiner lui-même gris.

    Lorsque vous définissez un formulaire Enabled de la propriété d' false, un indicateur supplémentaire est défini (dans le cadre, pas le sous-jacent sous-système Win32), que certains contrôles de vérifier lors de dessiner eux-mêmes. Ce drapeau est ce que raconte les contrôles de dessiner eux-mêmes dans un état désactivé.

    Donc, pour imiter ce qui se passerait avec ShowDialog, on doit régler le natif WS_DISABLED bit de style directement, au lieu de définir la forme de l' Enabled de la propriété d' false. Ceci est réalisé avec un tout petit peu de l'interopérabilité:

    const int GWL_STYLE   = -16;
    const int WS_DISABLED = 0x08000000;
    
    
    [DllImport("user32.dll")]
    static extern int GetWindowLong(IntPtr hWnd, int nIndex);
    
    
    [DllImport("user32.dll")]
    static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
    
    
    void SetNativeEnabled(bool enabled){
        SetWindowLong(Handle, GWL_STYLE, GetWindowLong(Handle, GWL_STYLE) &
            ~WS_DISABLED | (enabled ? 0 : WS_DISABLED));
    }
    
  3. L' ShowDialog() d'appel n'a pas retourné jusqu'à ce que la boîte de dialogue est fermée. C'est pratique, car vous pouvez suspendre la logique de votre propriétaire de la forme jusqu'à ce que le dialogue a fait de son entreprise. L' Show() appel, nécessairement, ne pas se comporter de cette façon. Par conséquent, si vous allez utiliser Show() au lieu de ShowDialog(), vous aurez besoin de casser votre logique en deux parties. Le code qui doit s'exécuter après la boîte de dialogue est rejeté (ce qui comprendrait la réactivation de la propriétaire de forme), doit être exécuté par un Closed de gestionnaire d'événements.

  4. Lorsqu'un formulaire est affiché comme une boîte de dialogue, paramètre de ses DialogResult de la propriété il se ferme automatiquement. Cette propriété est à chaque fois qu'un bouton avec un DialogResult des biens autres que des None est cliqué. Une forme affichée avec l' Show ne sera pas automatiquement de cette façon, nous devons fermer explicitement quand un de ses licenciement boutons est cliqué. Notez, cependant, que l' DialogResult propriété obtient toujours réglé de façon appropriée par le bouton.

La mise en œuvre de ces quatre choses, votre code devient quelque chose comme:

class FormB : Form{
    void Foo(){
        SetNativeEnabled(false); // defined above
        FormD f = new FormD();
        f.Closed += (s, e)=>{
            switch(f.DialogResult){
            case DialogResult.OK:
                // Do OK logic
                break;
            case DialogResult.Cancel:
                // Do Cancel logic
                break;
            }
            SetNativeEnabled(true);
        };
        f.Show(this);
        // function Foo returns now, as soon as FormD is shown
    }
}

class FormD : Form{
    public FormD(){
        Button btnOK       = new Button();
        btnOK.DialogResult = DialogResult.OK;
        btnOK.Text         = "OK";
        btnOK.Click       += (s, e)=>Close();
        btnOK.Parent       = this;

        Button btnCancel       = new Button();
        btnCancel.DialogResult = DialogResult.Cancel;
        btnCancel.Text         = "Cancel";
        btnCancel.Click       += (s, e)=>Close();
        btnCancel.Parent       = this;

        AcceptButton = btnOK;
        CancelButton = btnCancel;
    }
}

13voto

Marc Gravell Points 482669

Vous pouvez utiliser un thread séparé (comme ci-dessous), mais cela pénètre en territoire dangereux - vous ne devriez vous approcher de cette option que si vous comprenez les implications du thread (synchronisation, accès inter-thread, etc.):

 [STAThread]
static void Main() {
    Application.EnableVisualStyles();
    Button loadB, loadC;
    Form formA = new Form {
        Text = "Form A",
        Controls = {
            (loadC = new Button { Text = "Load C", Dock = DockStyle.Top}),
            (loadB = new Button { Text = "Load B", Dock = DockStyle.Top})
        }
    };
    loadC.Click += delegate {
        Form formC = new Form { Text = "Form C" };
        formC.Show(formA);            
    };
    loadB.Click += delegate {
        Thread thread = new Thread(() => {
            Button loadD;
            Form formB = new Form {
                Text = "Form B",
                Controls = {
                    (loadD = new Button { Text = "Load D",
                        Dock = DockStyle.Top})
                }
            };
            loadD.Click += delegate {
                Form formD = new Form { Text = "Form D"};
                formD.ShowDialog(formB);
            };
            formB.ShowDialog();  // no owner; ShowDialog to prevent exit
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
    };
    Application.Run(formA);
}
 

(évidemment, vous ne structureriez pas réellement le code comme ci-dessus - c'est juste la manière la plus courte de montrer le comportement; dans le vrai code, vous auriez une classe par formulaire, etc.)

11voto

TheSmurf Points 10872

Si vous exécutez le formulaire B sur un thread distinct de A et C, l'appel ShowDialog bloquera uniquement ce thread. De toute évidence, ce n'est pas un investissement trivial de travail bien sûr.

Vous pouvez faire en sorte que la boîte de dialogue ne bloque aucun thread en exécutant simplement l'appel ShowDialog de Form D sur un thread séparé. Cela nécessite le même type de travail, mais beaucoup moins, car vous n'aurez qu'un seul formulaire en cours d'exécution sur le thread principal de votre application.

6voto

Robert Venables Points 4599

Démarrez FormB dans un nouveau thread dans FormA:

         (new System.Threading.Thread(()=> {
            (new FormB()).Show();
        })).Start();
 

Maintenant, tous les formulaires ouverts dans le nouveau thread à l'aide de ShowDialog () ne bloqueront que FormB et NON FormA ou FormC

6voto

Justin Pihony Points 21088

Je voulais juste ajouter ma solution ici car elle semble bien fonctionner pour moi et peut être encapsulée dans une méthode d'extension simple. La seule chose que je dois faire est de gérer le clignotement comme @nightcoder a commenté la réponse de @ PDaddy.

 public static void ShowWithParentFormLock(this Form childForm, Form parentForm)
{
  childForm.ShowWithParentFormLock(parentForm, null);
}

public static void ShowWithParentFormLock(this Form childForm, Form parentForm, Action actionAfterClose)
{
  if (childForm == null)
    throw new ArgumentNullException("childForm");
  if (parentForm == null)
    throw new ArgumentNullException("parentForm");
  EventHandler activatedDelegate = (object sender, EventArgs e) =>
  {
    childForm.Focus();
    //To Do: Add ability to flash form to notify user that focus changed
  };
  childForm.FormClosed += (sender, closedEventArgs) =>
    {
      try
      {
        parentForm.Focus();
        if(actionAfterClose != null)
          actionAfterClose();
      }
      finally
      {
        try
        {
          parentForm.Activated -= activatedDelegate;
          if (!childForm.IsDisposed || !childForm.Disposing)
            childForm.Dispose();
        }
        catch { }
      }
    };
  parentForm.Activated += activatedDelegate;
  childForm.Show(parentForm);
}
 

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