2010-04-07 10 views
18

La mia applicazione dispone di diverse finestre "di primo livello" indipendenti, che hanno funzioni e flussi di lavoro completamente diversi.Finestra modale WPF utilizzando i blocchi ShowDialog() Tutte le altre finestre

Attualmente sto usando ShowDialog() per creare una modal Window WPF. La finestra modale è figlia di una delle finestre principali. Tuttavia, blocca tutte le finestre di primo livello dopo l'apertura. Vorrei che la finestra di dialogo blocchino SOLO la finestra principale da cui è stata lanciata. È possibile?

Non sono sicuro che sia importante, ma la finestra che apre la finestra di dialogo è la finestra iniziale dell'app, quindi tutte le altre finestre di primo livello vengono aperte da essa.

risposta

10

Un'opzione è di avviare le finestre che non si desidera siano interessate dalla finestra di dialogo su un thread diverso. Ciò potrebbe comportare altri problemi per l'applicazione, ma se tali finestre incapsulano realmente flussi di lavoro diversi, ciò potrebbe non essere un problema. Ecco alcuni esempi di codice che ho scritto per verificare che questo funziona:

<Window x:Class="ModalSample.MyWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Title="{Binding Identifier}" Height="150" Width="150"> 
    <StackPanel> 
     <TextBox Text="{Binding Identifier}" /> 
     <Button Content="Open Normal Child" Click="OpenNormal_Click" /> 
     <Button Content="Open Independent Child" Click="OpenIndependent_Click" /> 
     <Button Content="Open Modal Child" Click="OpenModal_Click" /> 
    </StackPanel> 
</Window> 

using System.ComponentModel; 
using System.Threading; 
using System.Windows; 

namespace ModalSample 
{ 
    /// <summary> 
    /// Interaction logic for MyWindow.xaml 
    /// </summary> 
    public partial class MyWindow : INotifyPropertyChanged 
    { 
     public MyWindow() 
     { 
      InitializeComponent(); 
      DataContext = this; 
     } 

     private int child = 1; 

     private string mIdentifier = "Root"; 
     public string Identifier 
     { 
      get { return mIdentifier; } 
      set 
      { 
       if (mIdentifier == value) return; 
       mIdentifier = value; 
       if (PropertyChanged != null) 
        PropertyChanged(this, new PropertyChangedEventArgs("Identifier")); 
      } 
     } 

     private void OpenNormal_Click(object sender, RoutedEventArgs e) 
     { 
      var window = new MyWindow {Identifier = Identifier + "-N" + child++}; 
      window.Show(); 
     } 

     private void OpenIndependent_Click(object sender, RoutedEventArgs e) 
     { 
      var thread = new Thread(() => 
       { 
        var window = new MyWindow {Identifier = Identifier + "-I" + child++}; 
        window.Show(); 

        window.Closed += (sender2, e2) => window.Dispatcher.InvokeShutdown(); 

        System.Windows.Threading.Dispatcher.Run(); 
       }); 

      thread.SetApartmentState(ApartmentState.STA); 
      thread.Start(); 
     } 

     private void OpenModal_Click(object sender, RoutedEventArgs e) 
     { 
      var window = new MyWindow { Identifier = Identifier + "-M" + child++ }; 
      window.ShowDialog(); 
     } 

     public event PropertyChangedEventHandler PropertyChanged; 
    } 
} 

ho sourced this blog post per l'esecuzione di una finestra WPF su un thread diverso.

5

Ho avuto lo stesso problema e implementato il comportamento finestra di dialogo modale come descritto in questo post: http://social.msdn.microsoft.com/Forums/vstudio/en-US/820bf10f-3eaf-43a8-b5ef-b83b2394342c/windowsshowmodal-to-parentowner-window-only-not-entire-application?forum=wpf

Ho anche provato un approccio più thread UI, ma questo ha causato problemi con le librerie di terze parti (Caliburn micro & Telerik wpf controls), poiché non sono progettati per essere utilizzati in più thread dell'interfaccia utente. È possibile farli funzionare con più thread dell'interfaccia utente, ma preferisco una soluzione più semplice ...

Se si implementa la finestra di dialogo come descritto, non è più possibile utilizzare la proprietà DialogResult, poiché causerebbe un "DialogResult può essere impostato solo dopo aver creato Finestra e visualizzato come finestra di dialogo "eccezione. Basta implementare la tua proprietà e usarla al suo posto.

sono necessari i seguenti finestre API di riferimento:

/// <summary> 
/// Enables or disables mouse and keyboard input to the specified window or control. 
/// When input is disabled, the window does not receive input such as mouse clicks and key presses. 
/// When input is enabled, the window receives all input. 
/// </summary> 
/// <param name="hWnd"></param> 
/// <param name="bEnable"></param> 
/// <returns></returns> 
[DllImport("user32.dll")] 
private static extern bool EnableWindow(IntPtr hWnd, bool bEnable); 

quindi utilizzare questo:

// get parent window handle 
IntPtr parentHandle = (new WindowInteropHelper(window.Owner)).Handle; 
// disable parent window 
EnableWindow(parentHandle, false); 
// when the dialog is closing we want to re-enable the parent 
window.Closing += SpecialDialogWindow_Closing; 
// wait for the dialog window to be closed 
new ShowAndWaitHelper(window).ShowAndWait(); 
window.Owner.Activate(); 

Questo è il gestore di eventi che riabilita la finestra padre, quando la finestra è chiusa:

private void SpecialDialogWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) 
{ 
    var win = (Window)sender; 
    win.Closing -= SpecialDialogWindow_Closing; 
    IntPtr winHandle = (new WindowInteropHelper(win)).Handle; 
    EnableWindow(winHandle, false); 

    if (win.Owner != null) 
    { 
     IntPtr parentHandle = (new WindowInteropHelper(win.Owner)).Handle; 
     // reenable parent window 
     EnableWindow(parentHandle, true); 
    } 
} 

E questo è lo ShowAndWaitHelper necessario per ottenere il comportamento del dialogo modale (questo blocca l'esecuzione del thread, ma esegue ancora il loop dei messaggi.

private sealed class ShowAndWaitHelper 
{ 
    private readonly Window _window; 
    private DispatcherFrame _dispatcherFrame; 
    internal ShowAndWaitHelper(Window window) 
    { 
     if (window == null) 
     { 
      throw new ArgumentNullException("window"); 
     } 
     _window = window; 
    } 
    internal void ShowAndWait() 
    { 
     if (_dispatcherFrame != null) 
     { 
      throw new InvalidOperationException("Cannot call ShowAndWait while waiting for a previous call to ShowAndWait to return."); 
     } 
     _window.Closed += OnWindowClosed; 
     _window.Show(); 
     _dispatcherFrame = new DispatcherFrame(); 
     Dispatcher.PushFrame(_dispatcherFrame); 
    } 
    private void OnWindowClosed(object source, EventArgs eventArgs) 
    { 
     if (_dispatcherFrame == null) 
     { 
      return; 
     } 
     _window.Closed -= OnWindowClosed; 
     _dispatcherFrame.Continue = false; 
     _dispatcherFrame = null; 
    } 
} 
+0

La tua risposta è stata incredibilmente utile. Ho creato una versione del metodo di estensione async Task ShowDialogAsync' di questo. – jnm2

Problemi correlati