Подтвердить что ты не робот

Можно ли использовать ShowDialog без блокировки всех форм?

Надеюсь, я могу объяснить это достаточно ясно. У меня есть моя основная форма (A), и она открывает 1 дочернюю форму (B), используя форму. Show() и вторую дочернюю форму (C), используя form.Show(). Теперь я хочу, чтобы дочерняя форма B открывала форму (D), используя form.ShowDialog(). Когда я это делаю, он также блокирует форму А и форму С. Есть ли способ открыть модальный диалог и заблокировать только его открытую форму?

4b9b3361

Ответ 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));
    }
}