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?
Réponses
Trop de publicités?À 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:
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 utilisezShow(owner)
. Alternativement, vous pouvez définir l'Owner
de la propriété de manière explicite, avec le même effet.-
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-sonWS_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 natifWS_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)); }
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 utiliserShow()
au lieu deShowDialog()
, 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 unClosed
de gestionnaire d'événements.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 unDialogResult
des biens autres que desNone
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;
}
}
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.)
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.
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);
}