Надеюсь, я могу объяснить это достаточно ясно. У меня есть моя основная форма (A), и она открывает 1 дочернюю форму (B), используя форму. Show() и вторую дочернюю форму (C), используя form.Show(). Теперь я хочу, чтобы дочерняя форма B открывала форму (D), используя form.ShowDialog(). Когда я это делаю, он также блокирует форму А и форму С. Есть ли способ открыть модальный диалог и заблокировать только его открытую форму?
Можно ли использовать ShowDialog без блокировки всех форм?
Ответ 1
Если вы запустите форму B в отдельном потоке из A и C, вызов ShowDialog будет блокировать только этот поток. Ясно, что это не тривиальное вложение работы, конечно.
У вас может быть диалог, который не блокирует любые потоки, просто запустив вызов формы D ShowDialog в отдельном потоке. Для этого требуется такая же работа, но гораздо меньше, так как у вас будет только одна форма, которая отключается от основного потока приложения.
Ответ 2
Использование нескольких потоков графического интерфейса - сложный бизнес, и я бы посоветовал ему, если это ваша единственная мотивация для этого.
А гораздо более подходящий подход - использовать Show()
вместо ShowDialog()
и отключить форму владельца до тех пор, пока не появится всплывающая форма. Существует всего четыре вопроса:
-
Когда используется
ShowDialog(owner)
, всплывающая форма остается на вершине своего владельца. То же самое можно сказать и при использованииShow(owner)
. В качестве альтернативы вы можете явно установить свойствоOwner
с тем же эффектом. -
Если вы установите для свойства владельца
Enabled
значениеfalse
, форма показывает отключенное состояние (дочерние элементы управления "серые" ), тогда как при использованииShowDialog
форма владельца сохраняется отключено, но не отображает отключенное состояние.Когда вы вызываете
ShowDialog
, форма владельца отключается в коде Win32, а бит стиляWS_DISABLED
устанавливается. Это заставляет его потерять способность набирать фокус и "дин" при нажатии, но не делает серию серого.Когда вы устанавливаете свойство
Enabled
формыfalse
, устанавливается дополнительный флаг (в рамках, а не подсистема подсистемы Win32), которые определенные элементы управления проверяют, когда они рисуют сами. Этот флаг указывает на то, что элементы управления приводят себя в отключенное состояние.Итак, чтобы подражать тому, что произойдет с
ShowDialog
, мы должны установить собственный бит стиляWS_DISABLED
непосредственно, вместо того чтобы установить для свойстваEnabled
значениеfalse
. Это достигается крошечным взаимодействием: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)); }
-
Вызов
ShowDialog()
не возвращается, пока диалог не будет отклонен. Это удобно, потому что вы можете приостановить логику в своей форме владельца до тех пор, пока диалог не сделает свою работу. ВызовShow()
, обязательно, не ведет себя так. Поэтому, если вы собираетесь использоватьShow()
вместоShowDialog()
, вам нужно разбить свою логику на две части. Код, который должен запускаться после отклонения диалога (который включает повторное включение формы владельца), должен выполняться обработчиком событияClosed
. -
Когда форма отображается как диалог, установка ее свойства
DialogResult
автоматически закрывает ее. Это свойство устанавливается, когда нажимается кнопка с свойствомDialogResult
, отличным отNone
. Форма, показанная с помощьюShow
, автоматически не закрывается таким образом, поэтому мы должны явно закрыть ее, когда нажата одна из его кнопок увольнения. Обратите внимание, однако, что свойствоDialogResult
по-прежнему устанавливается соответствующим образом кнопкой.
Реализация этих четырех вещей, ваш код станет чем-то вроде:
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;
}
}
Ответ 3
Вы можете использовать отдельный поток (как показано ниже), но это попадает на опасную территорию - вам стоит приблизиться к этой опции, если вы понимаете последствия потоковой передачи (синхронизация, доступ к кросс-потоку и т.д.):
[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);
}
(Очевидно, что на самом деле вы бы не структурировали код, как указано выше - это всего лишь кратчайший способ показать поведение, в реальном коде у вас будет класс для каждой формы и т.д.)
Ответ 4
Я хотел бы обобщить возможные решения и добавить новые альтернативы (3a и 3b). Но сначала я хочу уточнить, о чем мы говорим:
У нас есть приложение, которое имеет несколько форм. Существует требование показать модальное диалоговое окно, которое блокирует только определенные подмножества наших форм, но не другие. Модальные диалоги могут отображаться только в одном подмножестве (сценарий A) или нескольких подмножествах (сценарий B).
И теперь резюме возможных решений:
-
Не использовать модальные формы, отображаемые с помощью
ShowDialog()
вообщеПодумайте о дизайне вашего приложения. Вам действительно нужно использовать метод
ShowDialog()
? Если вам не нужна модальная форма, это самый простой и самый чистый способ пойти.Конечно, это решение не всегда подходит. Есть некоторые функции, которые дает нам
ShowDialog()
. Наиболее примечательным является то, что он отключает владельца (но не выделяется серым цветом), и пользователь не может взаимодействовать с ним. Очень изнурительный ответ был представлен P Daddy. -
Эмуляция поведения
ShowDialog()
Можно эмулировать поведение этого матода. Опять же, я рекомендую прочитать P Daddy answer.
a) Используйте комбинацию свойства
Enabled
наForm
и показывая форму как немодальную черезShow()
. В результате отключенная форма будет недоступна. Но это полностью управляемое решение без необходимости вмешательства.b) Не нравится, чтобы родительская форма была выделена серым цветом? Ссылка на несколько нативных методов и отключить бит
WS_DISABLED
в родительской форме (еще раз - см. Ответ от P Daddy).Эти два решения требуют, чтобы у вас был полный контроль над всеми диалоговыми окнами, которые вам нужно обрабатывать. Вы должны использовать специальную конструкцию, чтобы показать "частично блокирующий диалог" и не должны забывать об этом. Вам нужно настроить свою логику, потому что
Show()
не блокируется, аShowDialog()
блокируется. Проблема с системными диалогами (выборки файлов, подборщики цветов и т.д.) Может быть проблемой. С другой стороны, вам не нужен дополнительный код в формах, которые не должны блокироваться диалогом. -
Преодолеть ограничения
ShowDialog()
Обратите внимание, что есть
Application.EnterThreadModal
иApplication.LeaveThreadModal
. Это событие возникает, когда отображается модальный диалог. Остерегайтесь того, что события на самом деле являются нитями, а не общими.a) Прослушайте событие
Application.EnterThreadModal
в формах, которые не должны блокироваться диалогом и включитьWS_DISABLED
бит в этих формах. Вам нужно только настроить формы, которые не должны блокироваться модальными диалогами. Вам также может потребоваться проверить родительскую цепочку отображаемой модальной формы и переключитьWS_DISABLED
на основе этого условия (в вашем примере, если вам также необходимо открыть диалоги по формам A и C, но не блокировать формы B и D).b) Скрыть и повторно отобразить формы, которые не следует блокировать. Обратите внимание, что когда вы показываете новую форму после отображения модального диалога, она не блокируется. Воспользуйтесь этим и когда будет показано модальное диалоговое окно, скройте и снова отобразите нужные формы, чтобы они не были заблокированы. Однако этот подход может вызвать некоторое мерцание. Теоретически можно было бы установить включение/отключение перерисовки форм в Win API, но я не гарантирую этого.
c) Установите свойство
Owner
в форму диалога в формах, которые не должны блокироваться при отображении диалога. Я не тестировал это.d) Используйте несколько потоков графического интерфейса. Ответ от TheSmurf.
Ответ 5
Я просто хотел добавить мое решение здесь, поскольку он, похоже, хорошо работает для меня и может быть инкапсулирован в простой метод расширения. Единственное, что мне нужно сделать, это разобраться с миганием, поскольку @nightcoder прокомментировал ответ @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);
}
Ответ 6
Запустите FormB в новом потоке в FormA:
(new System.Threading.Thread(()=> {
(new FormB()).Show();
})).Start();
Теперь любые формы, открытые в новом потоке с помощью ShowDialog(), будут блокировать только FormB и NOT FormA или FormC
Ответ 7
У меня возникла аналогичная проблема в приложении, которое я писал. Мой основной пользовательский интерфейс был формой, работающей на основной теме. У меня был диалог помощи, который я хотел запустить как немодальный диалог. Это было легко реализовать, даже с точки зрения обеспечения того, чтобы у меня только один экземпляр диалогового окна справки. К сожалению, любые модальные диалоги, которые я использовал, заставляли диалоговое окно справки также потерять фокус - когда это было, когда некоторые из этих модальных диалогов выполнялись, что с помощью диалога помощи было бы наиболее полезно.
Используя идеи, упомянутые здесь, и в других местах, мне удалось преодолеть эту ошибку.
Я объявил поток внутри моего основного интерфейса.
Thread helpThread;
Следующий код посвящен событию, открытому в диалоговом окне справки.
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();
}
}
Мне также понадобилась инициализация потока, добавленного в мой конструктор, чтобы убедиться, что я не ссылался на нулевой объект при первом запуске этого кода.
public MainWindow()
{
...
helpThread = new Thread(new ThreadStart(startHelpThread));
helpThread.SetApartmentState(ApartmentState.STA);
...
}
Это гарантирует, что поток имеет только один экземпляр в любой момент времени. Сам поток запускает диалог и останавливается после закрытия диалога. Поскольку он работает в отдельном потоке, создание модального диалога из основного пользовательского интерфейса не приводит к зависанию диалогового окна справки. Мне нужно было добавить
helpDialog.Abort();
к событию закрытия формы моего основного пользовательского интерфейса, чтобы убедиться, что диалог справки закрывается, когда приложение завершено.
Теперь у меня есть немодальное диалоговое окно помощи, на которое не влияют какие-либо модальные диалоги, созданные из моего основного пользовательского интерфейса, и это именно то, что я хотел. Это безопасно, поскольку между основным интерфейсом и диалоговым окном справки отсутствует связь.
Ответ 8
Вот помощник, который я использую в WPF, чтобы запретить диалог блокировки окон без диалога на основе некоторых ответов на этот вопрос:
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));
}
}
Использование:
if(true == window.ShowDialogNonBlocking())
{
// Dialog result has correct value
}
Ответ 9
Возможно, детское окно (см. ChildWindow для более подробной информации) было бы более элегантным решением, и это позволило бы избежать всех проблем с отдельными потоки для графического интерфейса.
Ответ 10
Использование примера:
(new NoneBlockingDialog((new frmDialog()))).ShowDialogNoneBlock(this);
Исходный код:
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));
}
}