Laden...

Realisierung eines Optionen-Fensters (ähnlich Visual Studio)

Erstellt von robin_ vor 6 Jahren Letzter Beitrag vor 6 Jahren 3.579 Views
R
robin_ Themenstarter:in
38 Beiträge seit 2017
vor 6 Jahren
Realisierung eines Optionen-Fensters (ähnlich Visual Studio)

Hallo Leute.

Also, ich hab jetzt schon länger danach gesucht und probiert, aber leider keine Antwort finden können.

Wie realisiert man so ein Fenster (Siehe Anhang)? Klar, ListView für die Einträge, aber wie macht man es am geschicktesten, dass sich, je nach Eintrag, die Inhalte anpassen?

Danke 😃

MfG Robin

6.911 Beiträge seit 2009
vor 6 Jahren

Hallo robin_, willkommen im Forum,

das geht dann (relativ einfach) per Datenbindung. Schau dir dieses Thema in WPF einmal an, da siehst du worauf das hinausläuft. Auch Das Model-View-ViewModel (MVVM) Entwurfsmuster für WPF ist empfehlenswert dafür.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

M
177 Beiträge seit 2009
vor 6 Jahren

Vom Aufbau kannst du so vorgehen:

1.) Ein Window das links eine ListView oder ein Tree Control für die Navigation hat
2.) Rechts davon gibst du ein ContentControl
3.) Für jedes Navigations Element kannst du ein UserControl erstellen
4.) Wenn der User in der ListView auf ein Element klickt, tauscht du das ContentControl.Content durch das passende UserControl aus

Siehe auch:
Window vs Page vs UserControl for WPF navigation?

5.657 Beiträge seit 2006
vor 6 Jahren

3.) Für jedes Navigations Element kannst du ein UserControl erstellen
4.) Wenn der User in der ListView auf ein Element klickt, tauscht du das ContentControl.Content durch das passende UserControl aus

Das ist genau die falsche Herangehensweise. In WPF erstellt man nicht manuell UserControls, sondern verwendet DataBinding.

In dem Fall würde man die Auflistung mit den SettingsViewModels an den ListView (oder den TreeView) auf der linken Seite binden. Auf der rechten Seite würde man dann ein ContentControl an des selektierte Item binden, was dann den Editor für das selektierte ViewModel anzeigt.

Dafür kann man entweder den passenden Editor mittels Trigger auf IsSelected ein- bzw. ausblenden. Oder man verwendet dafür DataTemplates für die passende ViewModel-Klasse des ausgewählten Items.

Mehr Details dazu gibt es unter [Artikel] MVVM und DataBinding

Weeks of programming can save you hours of planning

M
177 Beiträge seit 2009
vor 6 Jahren

3.) Für jedes Navigations Element kannst du ein UserControl erstellen
4.) Wenn der User in der ListView auf ein Element klickt, tauscht du das ContentControl.Content durch das passende UserControl aus

Das ist genau die falsche Herangehensweise. In WPF erstellt man nicht manuell UserControls, sondern verwendet DataBinding.

Ich habe die Frage so verstanden, dass es darum geht komplette "Views" nach einem Navigation Item click zu laden. Mein Lösungsvorschlag beschreibt stark vereinfacht die Umsetzung eines RegionManager die in MVVM Frameworks wie PRISM Verwendung finden.

Außerdem schließt die Verwendung von UserControls ein DataBinding nicht aus.

R
robin_ Themenstarter:in
38 Beiträge seit 2017
vor 6 Jahren

Hi, glaube ich muss noch viel lesen, bin erst neu dabei 😃

Ich habe die Frage so verstanden, dass es darum geht komplette "Views" nach einem Navigation Item click zu laden

Ehrlich gesagt dachte ich, dass das der sinnvollste weg wäre, um nicht alles in einer Klasse zu haben.
Ich habe gerade mal angefangen den Artikel über das MVVM-Entwurfsmuster zu lesen, allerdings kann ich an zu vielen Stellen nicht folgen, z.B. schon direkt am Anfang 😄

  readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;  

Ich werde mir das zu Gemüte führen und mich dann wieder mit konkreten Fragen melden!

Danke und Grüße

MfG Robin

R
robin_ Themenstarter:in
38 Beiträge seit 2017
vor 6 Jahren

Ich habe die Frage so verstanden, dass es darum geht komplette "Views" nach einem Navigation Item click zu laden.

Genau das war meine Frage... Ich habe mich nun etwas mit MVVM beschäftigt. Aber nochmal zur anfänglichen Frage: Würde man das abgebildete Fenster ausschließlich mit einer XAML-Datei aufbauen (+ 1 ViewModel) und alles x-Tausend Elemente hiden, abhängig vom ausgewählten Item? kann ich mir nicht vorstellen.

Also ContentControl als Container, dieser kriegt dann mit Bindings ein "UserContentControl" (schätze mal eine eigene Klasse?) zugewiesen?

Jeder UserContentControl also wieder eine XAML, nur ohne Window - Definition, und mit eigenem ViewModel?

Danke für meine Verständnishilfe!

MfG Robin

MfG Robin

5.657 Beiträge seit 2006
vor 6 Jahren

Ich würde das wahrscheinlich mit DataTemplates umsetzen. Diese kannst du im der MainView oder in eigenen XAML-Dateien erstellen. Dann wird je nach Typ des links ausgewählten ViewModels rechts die im entsprechenden Template definierten Controls angezeigt:


<DataTemplate DataType="{x:Type GeneralSettingsViewModel}">
    <!-- Controls for General Settings Page -->
</DataTemplate>

Weeks of programming can save you hours of planning

R
robin_ Themenstarter:in
38 Beiträge seit 2017
vor 6 Jahren

Ich würde das wahrscheinlich mit DataTemplates umsetzen. Diese kannst du im der MainView oder in eigenen XAML-Dateien erstellen. Dann wird je nach Typ des links ausgewählten ViewModels rechts die im entsprechenden Template definierten Controls angezeigt:

  
<DataTemplate DataType="{x:Type GeneralSettingsViewModel}">  
    <!-- Controls for General Settings Page -->  
</DataTemplate>  
  

Könntest du mir noch ein paar Anhaltspunkte geben? Habe paar Sachen ausprobiert, komme aber leider nicht weiter, bzw. weiß ich nicht wirklich, womit ich Anfangen soll...

Folgendes: MainViewModel, welches eine ListCollectionView hält. Diese ist mit einer ObservableCollection<string> initialisiert und wird an die ListView gebunden --> zum probieren, es klappt schonmal.

Nun muss ich eine SettingsViewModel schreiben , die ObservableCollection zu <SettingsViewModel> ändern, und dann die Collection mit verschiedenen SettingsViewModel-Objekten füllen?

Wie verbinde ich nun diese Objekte 1. mit dem DataTemplate aus dem ResourcenWörterbuch und 2. wie binde ich dann DataTemplate des ausgewählten Elements an "ein Objekt" in der MainWindow, um es anzuzeigen?

Hoffe, dass mein Ansatz zumindest nicht ganz falsch ist.
Danke

P.S: Beim DataTemplate definiert man ja auch einen DataType... braucht man dafür dann nicht verschiedene Instanzen verschiedener Objekte, oder wie funktioniert die "Verknüpfung" zwischen SettingsViewModel-Instanz und dem DataTemplate?

MfG Robin

5.657 Beiträge seit 2006
vor 6 Jahren

Du brauchst eine Basisklasse BaseSettingsViewModel. Deine Collection im MainViewModel ist dann vom Typ ObservableCollection<BaseSettingsViewModel>. Diese Eigenschaft bindest du an die Liste auf der linken Seite. Das selektierte Item steht dann in ListView.SelectedItem, daran bindest du den Content der rechten Seite. Die Darstellung dort kann dann per DataTemplates gesteuert werden.

Dann brauchst du für jede Einstellung eine SettingsViewModel-Klasse, die von BaseSettingsViewModel abgeleitet ist, und alle Eigenschaften für die jeweiligen Einstellungen enthält.

Wie das mit den DataTemplates funktioniert, kannst du dir in der Doku anschauen, oder in [Artikel] MVVM und DataBinding

Weeks of programming can save you hours of planning

R
robin_ Themenstarter:in
38 Beiträge seit 2017
vor 6 Jahren

Sei mir nicht böse,
ich habe jetzt sowohl deinen Artikel als auch die Dokumentation durchstöbert, aber ich finde nirgends einen Hinweis darauf, was ich machen muss.

Klar, mit dem Snipped

 <ItemsControl Grid.Row="1">
                <ItemsControl.Resources>
                    <DataTemplate x:Key="Key?">
                        <StackPanel Orientation="Horizontal">
                            <Label Content="Text" />
                            <Label Content="Text2" />
                        </StackPanel>
                    </DataTemplate>
                    
                </ItemsControl.Resources>
            </ItemsControl>

Setzt man einen ItemsControl mit einem DataTemplate. Das DataTemplate könnte man nun bestimmt auslagern in eine andere Resource-Datei. Ok.

Aber wie und vor allem an was soll ich das Binden? Wie gesagt wird mir das nicht klar, weder aus der Doku noch aus deinem Artikel und bei einer generellen Suche habe ich auhc nichts gefunden (Was vielleicht auch daran liegen mag, dass ich nicht weiß, wonach ich da eigentlich suche?).

Danke.

MfG Robin

Information von MrSparkle vor 6 Jahren

Dropbox-Link entfernt. Bitte beachte [Hinweis] Wie poste ich richtig? Punkt 4, 4.1 und 4.2

5.657 Beiträge seit 2006
vor 6 Jahren

Hier steht alles, was man dazu wissen muß: Data Templating Overview. Da gibt es auch Beispielcode

Unter [Artikel] MVVM und DataBinding im Abschnitt Templates gibt es ein Beispiel, wie man die DataType-Eigenschaft des Templates verwendet.

In deinem Fall verwendest du zur Anzeige ein ContentControl anstatt des ItemsControl (da du ja nur ein Objekt zum Anzeigen hast, keine Auflistung), so in etwa:


<ContentControl Content="{Binding SelectedItem, ElementName=SettingsPagesListView}">
    <ContentControl.Resources>
        <DataTemplate DataType="{x:Type GeneralSettingsViewModel}">
            <!-- Controls for general settings -->  
        </DataTemplate>
         <DataTemplate DataType="{x:Type AdvancedSettingsViewModel}">
            <!-- Controls for advanced settings -->  
        </DataTemplate>
        <!-- etc. -->
    </ContentControl.Resources>
</ContentControl>
 

Weeks of programming can save you hours of planning

R
robin_ Themenstarter:in
38 Beiträge seit 2017
vor 6 Jahren

Danke für die Antwort, ich bin jetzt ein großes Stück weiter, mit

<ContentControl Grid.Row="2" Content="{Binding SelectedItem, ElementName=lstView}" >
                <ContentControl.Resources>
                    <DataTemplate  DataType="{x:Type s:SecondPageViewModel}">
                        <Button >Mein2.Button insourced</Button>

                    </DataTemplate>

                    <DataTemplate  DataType="{x:Type s:GeneralSettingsViewModel}">
                        <Button >Mein1.Button insourced</Button>

                    </DataTemplate>
                </ContentControl.Resources>
            </ContentControl>

Habe ich schonmal das DataTemplate vom entsprechend ausgewählten ViewModel als Content!

Nun bin ich gerade dabei, die DataTemplates in die Window.Resources auszulagern, um sie im nächsten Schritt sogar in ein ResourcesDictionary auszulagern.

Also:

 <Window.Resources>
        <s:DataBindingDebugConverter x:Key="debugConverter"/>

        <DataTemplate x:Key="SecondPageViewModel" DataType="{x:Type s:SecondPageViewModel}">
            <Button >Mein2.Button - Outsourced</Button>

        </DataTemplate>

        <DataTemplate x:Key="GeneralSettingsViewModel" DataType="{x:Type s:GeneralSettingsViewModel}">
            <Button >Mein1.Button - Oursourced</Button>

        </DataTemplate>

    </Window.Resources>

Und nun zum Code, der Leider nicht die Button anzeigt, sondern, ich schätze mal, den string der Methode ToString().

 <ContentControl Grid.Row="1"  Content="{Binding SelectedItem, ElementName=lstView}" ContentTemplate="{Binding SelectedItem.Name, ElementName=lstView}">
                

            </ContentControl>

Ich weiß, dass das Binding vom ContentTemplate keinen Sinn macht, schließlich brauche ich nicht den ToString(), um das richtige DataTemplate auszuwählen sondern BaseSettingsViewModel.Name

Wie binde ich das richtig?
Danke

P.S.: Ich habe deinen DebugConverter mal angeschmissen (Siehe Anhang).
Es kommt als Value schonmal der richtige Name raus: "GeneralSettingsViewModel".
Target type: System.Windows.DataTemplate scheint mir ebenfalls korrekt.

Eigentlich stimmt das doch soweit, oder irre ich mich?
LG und Danke!

MfG Robin

5.657 Beiträge seit 2006
vor 6 Jahren

Nun bin ich gerade dabei, die DataTemplates in die Window.Resources auszulagern

Dazu mußt du lediglich den Inhalt von ContentControl.Resources in Window.Resources verschieben. Sonst mußt du nichts ändern.

Weeks of programming can save you hours of planning

R
robin_ Themenstarter:in
38 Beiträge seit 2017
vor 6 Jahren

Danke, hat soweit alles geklappt.

Habe aber dennoch 2 kleine Fragen:

  1. Habe spaßeshalber paar Buttons mit Commands den jeweiligen ViewModels zugewiesen. Die Methoden werden auch aufgerufen. In der Methode steht zb. ein einfaches Console.WriteLine().
    Dieses wird aber, warum auch immer, erst ne halbe Sekunde o.ä. aufgerufen, sodass man den Knopf 3 mal drücken kann, bis man was sieht. Das kommt sehr träge rüber... Liegt das an der Konsolenausgabe oder an dem Command?

  2. Habe im 2. ViewModel ebenfalls einen Button. In der CanExecute gebe ich eine bool-Variable zurück, die mittels Timer erst nach 5 Sekunden auf true gesetzt wird. INotifyPropertyChanged ist implementiert (In der Base-Class). Komischer Weise bleibt der Button deaktiviert, bis ich entweder in den beiden Listeneinträgen hin und dann wieder zurück wechsle (was absolut logisch ist), oder jedoch,wenn ich irgendwo auf das Fenster klicke (Für mich weitaus weniger logisch).

Die Variable (bool) MyButtonActive wird im Timer_Elapsed - Handler geändert... kann das was damit zu tun haben?

Danke und MfG
Robin

MfG Robin

4.931 Beiträge seit 2008
vor 6 Jahren

Bzgl. CanExecute schau dir mal die Antworten in WPF - How to force a Command to re-evaluate 'CanExecute' via its CommandBindings an, also


CommandManager.InvalidateRequerySuggested();
// und evtl.
CommandManager.RequerySuggested += value;

R
robin_ Themenstarter:in
38 Beiträge seit 2017
vor 6 Jahren

Leider hat es damit schonmal nicht funktioniert 😕

Also das Timer-Event wird aufgerufen,
CommandManager.InvalidateRequerySuggested();
demnach auch, aber es ändert sich nicht.

Ehrlich gesagt verwundert mich dieses Verhalten... Ich dachte Bindings in Zusammenhang mit INotifyPropertyChanged wäre genau dafür da, dass sich die GUI updatet?

Achja, ist das ICommand - Verhalten normal, dass die Ausführung so lange dauert?

MfG Robin

R
robin_ Themenstarter:in
38 Beiträge seit 2017
vor 6 Jahren

Puh, ehrlich gesagt glaube ich nicht...

Vielleicht kannst du ja mal bei Gelegenheit drauf schauen?

Danke 😃

MfG Robin

4.931 Beiträge seit 2008
vor 6 Jahren

Die Zeitverzögerung kann ich nicht nachvollziehen - der 2. Button wird sofort aktiviert angezeigt (sowohl bei deiner beigefügten Anwendung als auch beim selbsterstellten Programm).

Das Nicht-Aktualisieren des Buttons beim "SecondPageViewModel" mittels des Times kann ich aber nachstellen. Mir ist bei deinem Sourcen nur aufgefallen, daß du beim RelayCommand das Event "CanExecuteChanged" ja niemals aufrufst.

Ich habe dann mal den Source bei mir geändert, aber auch das hat dann keinen Einfluß auf die Aktualisierung der UI gehabt - ich weiß, daß ich in einem früheren Projekt schon mal Probleme mit "CommandManager.InvalidateRequerySuggested()" hatte - und dann es anders gelöst habe (ich weiß aber nicht mehr wie - und an die Sourcen komme ich auch nicht mehr dran, sorry).

PS: Den Timer solltest du als Member der Klasse halten, sonst kann es sein, daß dieser schon vorher "disposed" wird und die Elapsed-Methode gar nicht mehr aufgerufen wird.

Edit:
Ich habe nochmal danach recherchiert.
Eine bessere Alternative ist es, direkt im ViewModel eine Eigenschaft 'IsEnabled' anzubieten und dieses dann an diese Button-Eigenschaft zu binden, s.a. Better Commands in WPF.
Auch wegen deinem Verzögerungsproblem gibt es die Empfehlung auf den CommandManager zu verzichten und ein auf dem DelegateCommand basierten Ansatz zu nehmen, s.a. CommandManager.InvalidateRequerySuggested() isn't fast enough. What can I do?.
(Deine bisherige RelayCommand-Klasse ist eher eine Mischung aus beiden, aber eben wegen fehlendem Aufruf vom "CanExecuteChanged" nicht korrekt).

So ich hoffe jetzt, daß du damit weiterkommst?