Question

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 B ouvre un formulaire (D) à l’aide de form.ShowDialog (). Lorsque je fais cela, il bloque la forme A et la forme C également. Existe-t-il un moyen d'ouvrir un dialogue modal et de ne le bloquer que pour le formulaire qui l'a ouvert?

Était-ce utile?

La solution

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

Vous pouvez demander à la boîte de dialogue de ne bloquer aucun thread en exécutant simplement l'appel ShowDialog du formulaire D sur un thread distinct. Cela nécessite le même type de travail, mais beaucoup moins, car vous n'aurez qu'un seul formulaire qui s'écoule du fil principal de votre application.

Autres conseils

L'utilisation de plusieurs threads d'interface graphique est une affaire délicate, et je vous le déconseille, si tel est votre seul motif.

Une approche bien plus appropriée consiste à utiliser Show () au lieu de ShowDialog () , et à désactiver le formulaire de propriétaire jusqu'au retour du formulaire contextuel. Il n'y a que quatre considérations:

  1. Lorsque ShowDialog (propriétaire) est utilisé, le formulaire contextuel reste au-dessus de son propriétaire. Il en va de même lorsque vous utilisez Show (propriétaire) . Vous pouvez également définir la propriété Owner de manière explicite, avec le même effet.

  2. Si vous définissez la propriété Enabled du formulaire propriétaire sur false , le formulaire affiche un état désactivé (les contrôles enfants sont "grisés"), alors que ShowDialog est utilisé, le formulaire propriétaire est toujours désactivé, mais n'affiche pas l'état désactivé.

    Lorsque vous appelez ShowDialog , le formulaire propriétaire est désactivé dans le code Win32 & # 8212; son bit de style WS_DISABLED est défini. Cela lui fait perdre la capacité de se concentrer et de "ding" lorsque vous cliquez dessus, mais ne le fait pas se dessiner en gris.

    Lorsque vous définissez la propriété Enabled d'un formulaire sur false , un indicateur supplémentaire est défini (dans le cadre, pas dans le sous-système Win32 sous-jacent) que certains contrôles vérifient lorsqu'ils s'affichent. se. Cet indicateur indique aux contrôles de se dessiner dans un état désactivé.

    Donc, pour émuler ce qui se produirait avec ShowDialog , définissons directement le bit de style natif WS_DISABLED , au lieu de définir la propriété Enabled du formulaire. vers false . Ceci est accompli avec un tout petit peu d’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'appel ShowDialog () ne retourne pas tant que la boîte de dialogue n'est pas fermée. C'est pratique, car vous pouvez suspendre la logique dans votre formulaire propriétaire jusqu'à ce que la boîte de dialogue ait fonctionné. L'appel Show () ne se comporte pas nécessairement de cette façon. Par conséquent, si vous souhaitez utiliser Show () au lieu de ShowDialog () , vous devrez diviser votre logique en deux parties. Le code à exécuter après la fermeture de la boîte de dialogue (ce qui inclut la réactivation du formulaire propriétaire) doit être exécuté par un gestionnaire d'événements Closed .

  4. Lorsqu'un formulaire s'affiche en tant que boîte de dialogue, la définition de sa propriété DialogResult le ferme automatiquement. Cette propriété est définie chaque fois que vous cliquez sur un bouton avec une propriété DialogResult autre que Aucune . Un formulaire affiché avec Show ne se fermera pas automatiquement de cette façon. Nous devons donc le fermer explicitement lorsque l’on clique sur l’un de ses boutons de renvoi. Notez cependant que la propriété DialogResult est toujours définie correctement par le bouton.

En mettant en œuvre 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;
    }
}

Vous pouvez utiliser un thread séparé (comme ci-dessous), mais cela entre dans un territoire dangereux - vous ne devriez vous approcher de cette option que si vous comprenez les implications du thread (synchronisation, accès cross-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 structurez pas le code comme ci-dessus - il s’agit simplement du moyen le plus rapide de montrer le comportement; dans le code réel, vous auriez une classe par formulaire, etc.)

Je voudrais résumer les solutions possibles et en ajouter une nouvelle (3a et 3b). Mais je veux d’abord clarifier ce dont nous parlons:

Nous avons une application qui a plusieurs formes. Il est nécessaire d’afficher un dialogue modal qui ne bloque que certains sous-ensembles de nos formulaires, mais pas les autres. Les dialogues modaux peuvent apparaître uniquement dans un sous-ensemble (scénario A) ou dans plusieurs sous-ensembles (scénario B).

Et maintenant résumé des solutions possibles:

  1. N'utilisez pas de formulaires modaux affichés via ShowDialog () du tout

    Pensez à la conception de votre application. Avez-vous vraiment besoin d'utiliser la méthode ShowDialog () ? Si vous n’avez pas besoin de la forme modale, c’est le moyen le plus simple et le plus propre.

    Bien sûr, cette solution ne convient pas toujours. ShowDialog () nous offre certaines fonctionnalités. Le plus notable est qu'il désactive le propriétaire (mais ne le grise pas) et que l'utilisateur ne peut pas interagir avec lui. La réponse très épuisante fournie P papa .

  2. Émulation du comportement ShowDialog ()

    Il est possible d'émuler le comportement de cette méthode. Encore une fois, je recommande de lire la réponse de P Daddy .

    a) Utilisez la combinaison de la propriété Enabled sur Form et affichez le formulaire sous forme non modale via Afficher () . En conséquence, le formulaire désactivé sera grisé. Mais c’est une solution complètement gérée sans aucune intervention nécessaire.

    b) Vous n'aimez pas que le formulaire parent soit grisé? Référencez quelques méthodes natives et désactivez le code WS_DISABLED du formulaire parent (à nouveau - voir la réponse dans P Papa ).

    Ces deux solutions requièrent un contrôle total sur toutes les boîtes de dialogue que vous devez gérer. Vous devez utiliser une construction spéciale pour afficher " le dialogue partiellement bloquant " et ne doit pas l'oublier. Vous devez ajuster votre logique car Show () n'est pas bloquant et ShowDialog () est bloquant. Le traitement des dialogues système (sélecteurs de fichiers, sélecteurs de couleurs, etc.) peut poser problème. D'autre part, vous n'avez pas besoin de code supplémentaire sur les formulaires qui ne doivent pas être bloqués par dialogue.

  3. Surmontez les limitations de ShowDialog ()

    Notez qu'il existe Application.EnterThreadModal et Application.LeaveThreadModal . Cet événement est déclenché chaque fois que la boîte de dialogue modale est affichée. Notez que les événements s’appliquent à l’ensemble du thread, pas à celui de l’application.

    a) Écoutez l'événement Application.EnterThreadModal dans des formulaires qui ne doivent pas être bloqués par la boîte de dialogue et activer le code WS_DISABLED . sous ces formes. Vous devez uniquement ajuster des formulaires qui ne doivent pas être bloqués par des boîtes de dialogue modales. Vous devrez peut-être également inspecter la chaîne parente du formulaire modal affiché et basculer WS_DISABLED en fonction de cette condition (dans votre exemple, vous devez également ouvrir des boîtes de dialogue à l'aide des formulaires A et C, mais pas bloquer). formes B et D).

    b) Masquer et afficher à nouveau les formulaires à ne pas bloquer . Notez que lorsque vous affichez un nouveau formulaire après que la boîte de dialogue modale s'affiche, celle-ci n'est pas bloquée. Profitez de cela et lorsque la boîte de dialogue modale est affichée, masquez et affichez à nouveau les formulaires souhaités afin qu'ils ne soient pas bloqués. Cependant, cette approche peut amener des scintillements. Cela pourrait être théoriquement résolu en activant / désactivant le repeinte des formulaires dans Win API, mais je ne le garantis pas.

    c) Définissez la propriété Owner sur la fiche de dialogue sur les formulaires à ne pas bloquer lorsque la boîte de dialogue est affichée. Je n'ai pas testé cela.

    d) Utilisez plusieurs threads d'interface graphique . Réponse de TheSmurf .

Démarrer FormB dans un nouveau thread dans FormA:

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

Désormais, tout formulaire ouvert dans le nouveau fil à l'aide de ShowDialog () ne bloquera que FormB et NOT FormA ou FormC

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 alors que @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);
}

Je rencontrais un problème similaire dans une application que j'écrivais. Mon interface utilisateur principale était un formulaire fonctionnant sur le thread principal. J'avais un dialogue d'aide que je voulais exécuter en tant que dialogue non modal. Cela était facile à mettre en œuvre, même au point de m'assurer que seule une instance de la boîte de dialogue d'aide était en cours d'exécution. Malheureusement, toutes les boîtes de dialogue modales que j'ai utilisées ont également pour effet de faire perdre le focus à la boîte de dialogue d’aide. Lorsque certains de ces boîtes de dialogue modales étaient en cours d’exécution, la présence de la boîte de dialogue d’aide serait plus utile.

En utilisant les idées mentionnées ici et ailleurs, j'ai réussi à surmonter ce bug.

J'ai déclaré un fil à l'intérieur de mon interface utilisateur principale.

Thread helpThread;

Le code suivant traite de l'événement déclenché pour ouvrir la boîte de dialogue d'aide.

private void Help(object sender, EventArgs e)
{
    //if help dialog is still open then thread is still running
    //if not, we need to recreate the thread and start it again
    if (helpThread.ThreadState != ThreadState.Running)
    {
        helpThread = new Thread(new ThreadStart(startHelpThread));
        helpThread.SetApartmentState(ApartmentState.STA);
        helpThread.Start();
    }
}

void startHelpThread()
{
    using (HelpDialog newHelp = new HelpDialog(resources))
    {
        newHelp.ShowDialog();
    }
}

J'avais également besoin de l'initialisation du thread ajouté à mon constructeur pour m'assurer que je ne faisais pas référence à un objet null lors de la première exécution de ce code.

public MainWindow()
{
    ...
    helpThread = new Thread(new ThreadStart(startHelpThread));
    helpThread.SetApartmentState(ApartmentState.STA);
    ...
}

Cela garantit que le thread n'a qu'une seule instance à la fois. Le fil lui-même exécute la boîte de dialogue et s'arrête une fois celle-ci fermée. Comme il est exécuté sur un thread séparé, la création d'une boîte de dialogue modale à partir de l'interface utilisateur principale ne provoque pas le blocage de la boîte de dialogue d'aide. J'ai eu besoin d'ajouter

helpDialog.Abort();

à l'événement de clôture de formulaire de mon interface utilisateur principale pour vous assurer que la boîte de dialogue d'aide se ferme à la fermeture de l'application.

J'ai maintenant une boîte de dialogue d'aide non modale qui n'est pas affectée par les boîtes de dialogue modales générées à partir de mon interface utilisateur principale, ce qui est exactement ce que je voulais. Ceci est sûr car aucune communication n'est nécessaire entre l'interface utilisateur principale et le dialogue d'aide.

Voici l'aide que j'utilise dans WPF pour empêcher la boîte de dialogue de bloquer les fenêtres autres que la boîte de dialogue en fonction de certaines réponses à cette question:

public static class WindowHelper
{
    public static bool? ShowDialogNonBlocking(this Window window)
    {
        var frame = new DispatcherFrame();

        void closeHandler(object sender, EventArgs args)
        {
            frame.Continue = false;
        }

        try
        {
            window.Owner.SetNativeEnabled(false);
            window.Closed += closeHandler;
            window.Show();

            Dispatcher.PushFrame(frame);
        }
        finally
        {
            window.Closed -= closeHandler;
            window.Owner.SetNativeEnabled(true);
        }
        return window.DialogResult;
    }

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

    static void SetNativeEnabled(this Window window, bool enabled)
    {
        var handle = new WindowInteropHelper(window).Handle;
        SetWindowLong(handle, GWL_STYLE, GetWindowLong(handle, GWL_STYLE) &
            ~WS_DISABLED | (enabled ? 0 : WS_DISABLED));
    }
}

Utilisation:

if(true == window.ShowDialogNonBlocking())
{
    // Dialog result has correct value
}

Utilisation de l'exemple:

(new NoneBlockingDialog((new frmDialog()))).ShowDialogNoneBlock(this);

Code source:

class NoneBlockingDialog
{
    Form dialog;
    Form Owner;

    public NoneBlockingDialog(Form f)
    {
        this.dialog = f;
        this.dialog.FormClosing += new FormClosingEventHandler(f_FormClosing);
    }

    void f_FormClosing(object sender, FormClosingEventArgs e)
    {
        if(! e.Cancel)
            PUtils.SetNativeEnabled(this.Owner.Handle, true);
    }

    public void ShowDialogNoneBlock(Form owner)
    {
        this.Owner = owner;
        PUtils.SetNativeEnabled(owner.Handle, false);
        this.dialog.Show(owner);
    }
}

partial class PUtils
{
            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);


    static public void SetNativeEnabled(IntPtr hWnd, bool enabled)
    {
        SetWindowLong(hWnd, GWL_STYLE, GetWindowLong(hWnd, GWL_STYLE) & ~WS_DISABLED | (enabled ? 0 : WS_DISABLED));
    }
}

Peut-être une fenêtre enfant (voir ChildWindow pour plus de détails) constituerait une solution plus élégante et éviterait tous les problèmes liés aux threads séparés pour l'interface graphique.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top