2010-05-14 10 views
6

Ich möchte ein WPF-Fenster erstellen, das sich als modale Dialogbox verhält und gleichzeitig ausgewählte Operationen in bestimmten anderen Fenstern der gleichen Anwendung erleichtert. Ein Beispiel für dieses Verhalten ist in Adobe Photoshop zu sehen, das mehrere Dialoge bietet, in denen der Benutzer mithilfe einer Pipette eine Auswahl aus einem Bild treffen und praktisch alle anderen Anwendungsfunktionen deaktivieren kann.Ein Fenster, das sich sowohl modal als auch nichtmodal verhält

Ich schätze, dass der Weg nach vorne darin besteht, einen nicht-modalen, immer-auf-dem-Kopf-Dialog zu erstellen und programmatisch solche Anwendungsfunktionen zu deaktivieren, die für den Dialog nicht anwendbar sind. Gibt es einen einfachen Weg, dies in WPF zu erreichen? Oder vielleicht gibt es ein Designmuster, das ich übernehmen könnte.

+0

Ich glaube, Sie auf dem richtigen Weg sind. Sie können die Steuerelemente in CodeBehind entweder deaktivieren/aktivieren, wenn das Fenster geöffnet/geschlossen wird, oder Sie können DataTriggers in Ihrem XAML verwenden und die Eigenschaft 'IsEnabled' für die verschiedenen Steuerelemente festlegen, abhängig davon, ob das neue Fenster geöffnet ist oder nicht. –

Antwort

0

Was Sie suchen, ist ähnlich wie Multiple Document Interface. Dies ist nicht standardmäßig in WPF verfügbar, aber es gibt einige Bemühungen, dies zu unterstützen, sowohl free als auch commercial.

Es liegt an Ihnen, den aktuellen Status der Anwendung zu identifizieren und Benutzeroberflächenelemente als Reaktion darauf zu aktivieren/deaktivieren.

+0

Während ich ein MDI baue, unterstützt die Eigenart eines MDI das beschriebene Verhalten nicht. Ich stimme deinem zweiten Absatz zu, aber es gibt ein potentielles Kopfzerbrechen: Wenn ich ICommands deaktivieren möchte, dann brauche ich eine Möglichkeit, dies programmatisch zu tun, und nachdem ich ein wenig darüber nachgedacht habe, scheint es eine Menge Arbeit zu sein. – Chris

0

Ich denke, ein immer-auf-Top-Windows, das bestimmte App-Funktionen programmgesteuert deaktiviert, ist die Möglichkeit, dies zu tun. Es könnte einfacher sein, eine "weiße Liste" von Funktionen beizubehalten, die aktiviert werden können, während dieses Formular geöffnet ist, und dann alles zu deaktivieren, was nicht in der Liste ist (im Gegensatz zu dem Versuch, eine "schwarze Liste" von allem zu führen) kann nicht aktiviert sein).

2

Ja, es gibt den traditionellen Ansatz, den Sie beschreiben, wo Sie Funktionen programmatisch aktivieren/deaktivieren, aber WPF eröffnet auch einige neue Möglichkeiten, die in WinForms und älteren Technologien wirklich nicht möglich waren.

werde ich vier WPF-spezifische Weise erklären, dies zu tun:

  • Sie heimlich und automatisch einen Fensterinhalt mit einem Bild ersetzen seines Inhalts ein Rechteck mit einem VisualBrush verwendet wird, wodurch effektiv zu deaktivieren es. Für den Benutzer sieht es so aus, als ob das Fenster unverändert ist, aber der tatsächliche Inhalt wird unter dem Bild angezeigt, so dass Sie es für das Testen von Treffern verwenden und ausgewählte Ereignisse sogar dorthin weiterleiten können.

  • Sie können ein MergedDictionary zum ResourceDictionary Ihres Fensters hinzufügen, das bewirkt, dass alle TextBoxen zu TextBlocks werden, alle Buttons deaktiviert werden usw., außer dass explizit benutzerdefinierte angefügte Eigenschaften überschrieben werden. Anstatt alle Benutzeroberflächen selektiv durchzuschalten oder zu deaktivieren, fügen Sie einfach ein Objekt aus der MergedDictionaries-Auflistung hinzu oder entfernen es.

  • Sie können den InputManager verwenden, um echte Mausereignisse in bestimmten Teilen eines deaktivierten Fensters programmgesteuert zu generieren und zu verarbeiten. Mausereignisse, die keinen Hit-Test durchführen, werden nicht genehmigt."

  • Verwendung der Datenbindung und Stile aktivieren/deaktivieren einzelne Steuerelemente, statt durch sie Iterieren

Details zu

Für diese Lösung Fenster mit dem Bild der Fenster zu ersetzen, wiederholen Sie Ihre App Windows und ersetzen Sie jeden Inhalt durch ein Grid, das den ursprünglichen Inhalt und ein Rechteck enthält, wie folgt:

<Window ...> 
    <Grid> 
    <ContentPresenter x:Name="OriginalContent" /> 
    <Rectangle> 
     <Rectangle.Fill> 
     <VisualBrush Visual="{Binding ElementName=OriginalContent}" /> 
     </Rectangle.Fill> 
    </Rectangle> 
    </Grid> 
</Window> 

Dies kann programmgesteuert oder mithilfe einer Vorlage im Fenster erfolgen, aber ich möchte ein benutzerdefiniertes Steuerelement erstellen und die obige Struktur mithilfe seiner Vorlage erstellen. Wenn dies geschehen ist, können Sie Ihre Fenster, wie einfach diesen Code:

<Window ...> 
    <my:SelectiveDisabler> 
    <Grid x:Name="LayoutRoot"> ... </Grid> <!-- Original content --> 
    </my:SelectiveDisabler> 
</Window> 

Durch das Hinzufügen Maus-Event-Handler auf das Rechteck und riefen VisualTreeHelper.HitTest auf dem Content, um zu bestimmen, welches Objekt wurde in dem ursprünglichen Inhalt angeklickt. Von diesem Punkt aus können Sie wählen, das Mausereignis zu ignorieren, es an den ursprünglichen Inhalt zur Verarbeitung weiterzuleiten oder im Falle einer Pipettensteuerung oder einer Objektauswahlfunktion einfach die gewünschten Objekte/Informationen zu extrahieren.

Details zu MergedDictionary Ansatz

Natürlich können Sie Ihre ganze UI restyle ein Resource verschmolzen in die Fenster der Ressourcen.

Eine einfache Möglichkeit besteht darin, einfach implizite Stile im zusammengeführten ResourceDictionary zu erstellen, damit alle TextBoxen als TextBlocks erscheinen, alle Schaltflächen als Rahmen usw. Dies funktioniert nicht sehr gut, da jede TextBox mit einem eigenen Stil oder ControlTemplate, das explizit festgelegt wird, kann die Updates verpassen. Darüber hinaus erhalten Sie möglicherweise nicht alle Objekte wie gewünscht, und es gibt keine Möglichkeit zum einfachen Entfernen der Befehle oder Klicken Sie auf Ereignisse von Schaltflächen, da sie explizit angegeben sind und der Stil diese nicht überschreibt.

Eine bessere Möglichkeit, dies zu erreichen, ist, dass die Stile im zusammengeführten ResourceDictionary eine angefügte Eigenschaft festlegen und anschließend CodeHinding im PropertyChangedCallback verwenden, um die Eigenschaften zu aktualisieren, die Sie wirklich ändern möchten. Wenn Sie die Eigenschaft "ModalMode" als Eigenschaft festlegen, werden alle lokalen Werte und Bindungen für eine Reihe von Eigenschaften (Template, Command, Click, IsEnabled usw.) in einer privaten DependencyProperty für das Objekt gespeichert und dann mit Standardwerten überschrieben . Zum Beispiel würde die Eigenschaft Command einer Schaltfläche vorübergehend auf null gesetzt. Wenn die angehängte Eigenschaft "ModalMode" auf "false" gesetzt wird, werden alle ursprünglichen lokalen Werte und Bindungen aus dem temporären Speicher zurückkopiert, und der temporäre Speicher wird gelöscht.

Diese Methode bietet eine praktische Möglichkeit, Teile Ihrer Benutzeroberfläche selektiv zu aktivieren/deaktivieren, indem einfach eine weitere angefügte Eigenschaft "IgnoreModalMode" hinzugefügt wird. Sie können dies für alle UIElements, für die die ModalMode-Änderungen nicht gelten sollen, manuell auf True setzen. Ihr ModalMode PropertyChangedCallback prüft das dann und wenn es wahr ist, tut es nichts.

Details zu Inputmanager Ansatz

Wenn Sie die Maus erfassen, Sie Mauskoordinaten egal bekommen kann, wo es bewegt wird. Übersetzen Sie diese in Bildschirmkoordinaten mit CompositionTarget.TransformToDevice(), und verwenden Sie dann CompositionTarget.TransformFromDevice() für jedes Kandidatenfenster.Wenn sich die Mauskoordinaten in Grenzen befinden, testen Sie das deaktivierte Fenster (dies kann auch dann noch durchgeführt werden, wenn ein Fenster deaktiviert ist). Wenn Sie das Objekt, auf das der Benutzer geklickt hat, verwenden möchten, verwenden Sie InputManager.ProcesInput, um das Mausereignis zu verursachen im anderen Fenster genauso verarbeitet, als ob es nicht deaktiviert wäre.

Einzelheiten zur Verwendung von Daten

Sie Bindung kann ein Stile verwenden, um die IsEnabled Eigenschaft von Buttons zu binden, MenuItems, usw. auf einen statischen Wert wie folgt aus:

<Setter Property="IsEnabled" Value="{Binding NonModal, Source={x:Static local:ModalModeTracker.Instance}}" /> 

nun standardmäßig alle Elemente Mit diesen Stilen wird automatisch deaktiviert, wenn Ihre NonModal-Eigenschaft falsch wird. Jedes einzelne Steuerelement kann jedoch mit IsEnabled="true" überschrieben werden, um auch im modalen Modus aktiviert zu bleiben. Komplexere Bindungen können mit MultiBinding und EDF ExpressionBinding erstellt werden, um die gewünschten Regeln festzulegen.


Keiner dieser Ansätze durch Ihre visuelle Schnittstelle erfordern laufen, Aktivieren und Deaktivieren von Funktionen. Welche dieser Optionen tatsächlich ausgewählt wird, hängt davon ab, welche Funktionalität Sie im modalen Modus tatsächlich bereitstellen möchten und wie der Rest der Benutzeroberfläche gestaltet ist.

In jedem Fall macht WPF das viel einfacher als in WinForms Tagen. Waffst du nicht einfach WPF's Power?

0

Ich glaube, der beste Ansatz zur Lösung dieses Problems ist die Verwendung des eingangs erwähnten InputManager-Ansatzes. Mit diesem Entwurfsmuster können Sie Befehle mit Ihren Symbolleistenschaltflächen/Menüelementen usw. verbinden, und jeder Befehl ruft einen CanExecute-Handler auf, den Sie für Ihren Befehl angeben. In diesem Handler würden Sie den Befehl so einstellen, dass er nicht aktiviert wird, wenn das immer geöffnete nicht-modale Fenster geöffnet ist.

http://msdn.microsoft.com/en-us/library/ms752308.aspx