none
WPF-MVVM Mehrere ViewModels in einem Window binden RRS feed

  • Frage

  • Hi,

    ich hatte hier folgenden Thread Datenbindung zwischen Child und Parent in einer Observable Collection (eigene Klasse), welcher von Koopakiller beantwortet wurde.

    Dabei wurde eine Klasse namens MyItem erstellt. Das ist im Endeffekt eine Observable Collection von MyItem welche den DataContext vom Window belegt mit der kleinen Zeile:

    this.DataContext = this;

    Jetzt will ich später aber, wegen dutzender Datenbankabfragen die gleichzeitig stattfinden, eine ProgressBar mit einbinden. Diese benötigt aber ViewModels, soweit ich das verstanden habe. Um ein ViewModel einzubinden soll man (zumindest laut allen Erklärungen die ich fand)

    DataContext = new MyViewModel();

    setzen.

    Meine Frage wäre jetzt: Wie muss ich hier vorgehen? Wie bekomme ich MyItem in das ViewModel oder wie binde ich beides in den DataContext?

    Donnerstag, 6. November 2014 09:53

Antworten

  • Hi,

    jedes FrameworkElement hat eine Eigenschaft DataContext. Ist die in einem Element nicht gesetzt, wird die des übergeordneten Elementes geerbt.

    Deswegen setzt man oftmals den DataContext für ein Window und vererbt damit an alle Element, wie z.B. einen ProgressBar den DataContext.

    Hat man jetzt, so wie in deinem Fall, die Anforderung in einem Window ein oder mehrere Elemente einem anderen DataContext zuzuweisen, so setzt man den für das entsprechende Element neu.

    Wenn du einen ViewModelLocator, wie z.B. aus MVVMLight verwendest, kann dein XAML dann wie folgt aussehen:

    <ProgressBar DataContext="{Binding ProgressBarViewModel, Source={StaticResource Locator}}">


    Andreas Richter
    Softwareentwickler und -architekt
    http://www.anrichter.net

    • Als Antwort markiert Marcel Gpunkt Donnerstag, 6. November 2014 13:07
    • Tag als Antwort aufgehoben Marcel Gpunkt Donnerstag, 6. November 2014 13:14
    • Als Antwort markiert Marcel Gpunkt Donnerstag, 6. November 2014 13:59
    Donnerstag, 6. November 2014 11:39
  • Das geht natürlich auch:

    <Window x:Class....
            xmlns:vms="clr-namespace:DeineApp.ViewModel"
            .... />
    
    ...
    
    <Window.DataContext>
        <vms:MainViewModel />
    </Window.DataContext>
    
    ...
    
    <ProgressBar>
        <ProgressBar.DataContext>
            <vms:ProgessBarViewModel />
        </ProgressBar.DataContext>
    </ProgressBar>
    



    Andreas Richter
    Softwareentwickler und -architekt
    http://www.anrichter.net

    • Als Antwort markiert Marcel Gpunkt Donnerstag, 6. November 2014 13:06
    • Tag als Antwort aufgehoben Marcel Gpunkt Donnerstag, 6. November 2014 13:14
    • Als Antwort markiert Marcel Gpunkt Donnerstag, 6. November 2014 13:58
    Donnerstag, 6. November 2014 12:43
  • Ändere mal

    xmlns:vms="clr-namespace:ASAVideowand.ProgressBarViewModel"


    in

    xmlns:vms="clr-namespace:ASAVideowand"

    Bringt das was ? Die Namespace-Deklaration braucht ja nur den Namespace selber und nicht das ViewModel, ads benutzt wird.

    Claudius

    • Als Antwort markiert Marcel Gpunkt Donnerstag, 6. November 2014 13:58
    Donnerstag, 6. November 2014 13:52

Alle Antworten

  • Hallo Taldrit,

    ein Weg, das zu machen, wäre eine Klasse zu erstellen, die als Repräsentierung des DataContexts gilt und deren Properties als die gewünschten ViewModels zu deklarieren.

    Bsp:

    public class DataContextKlasse : INotifyPropertyChanged
    {
    
    public ViewModel1 VM1 {get; set;}
    public ViewModel2 VM2 {get; set;}
    
    }
    
    
    DataContextKlasse dataContext = new DataContextKlasse ();
    
    dataContext.VM1 = new ViewModel1 ();
    dataContext.VM2 = new ViewModel2 ();
    
    DataContext = dataContext;
    
    

    Dann kannst du einen 'globalen' DataContext erstellen, über dessen Properties zugegriffen und geupdated werden kann.

    Ist wie gesagt, ein Lösungsweg, den ich auch mal genutzt habe, da man eben nur einen DataContext festlegen kann.

    Gruß Claudius

    Donnerstag, 6. November 2014 10:09
  • Hi,

    jedes FrameworkElement hat eine Eigenschaft DataContext. Ist die in einem Element nicht gesetzt, wird die des übergeordneten Elementes geerbt.

    Deswegen setzt man oftmals den DataContext für ein Window und vererbt damit an alle Element, wie z.B. einen ProgressBar den DataContext.

    Hat man jetzt, so wie in deinem Fall, die Anforderung in einem Window ein oder mehrere Elemente einem anderen DataContext zuzuweisen, so setzt man den für das entsprechende Element neu.

    Wenn du einen ViewModelLocator, wie z.B. aus MVVMLight verwendest, kann dein XAML dann wie folgt aussehen:

    <ProgressBar DataContext="{Binding ProgressBarViewModel, Source={StaticResource Locator}}">


    Andreas Richter
    Softwareentwickler und -architekt
    http://www.anrichter.net

    • Als Antwort markiert Marcel Gpunkt Donnerstag, 6. November 2014 13:07
    • Tag als Antwort aufgehoben Marcel Gpunkt Donnerstag, 6. November 2014 13:14
    • Als Antwort markiert Marcel Gpunkt Donnerstag, 6. November 2014 13:59
    Donnerstag, 6. November 2014 11:39
  • Hi Claudius,

    wie in meiner obigen Antwort schon geschrieben, kannst du im XAML für jedes Element den DataContext setzen. Damit kannst du dir das Erstellen deiner DataContextKlasse sparen.


    Andreas Richter
    Softwareentwickler und -architekt
    http://www.anrichter.net

    Donnerstag, 6. November 2014 11:41
  • Hallo Andreas,

    ja, das stimmt. Meine Idee ist ja, wie schon gesagt, nur eine Möglichkeit.

    Die StaticResource wäre auch meine 2. Lösung gewesen :)

    Gruß Claudius

    Donnerstag, 6. November 2014 12:01
  • Ja. Deine Idee funktionieren natürlich auch.
    Viele Wege führen nach Rom :)

    Andreas Richter
    Softwareentwickler und -architekt
    http://www.anrichter.net

    Donnerstag, 6. November 2014 12:14
  • Wenn du einen ViewModelLocator, wie z.B. aus MVVMLight verwendest, kann dein XAML dann wie folgt aussehen:
    <ProgressBar DataContext="{Binding ProgressBarViewModel, Source={StaticResource Locator}}">
    Diese Idee hört sich schon toll an, nur kann ich mit einem Locator nicht viel anfangen. Kann ich auch irgendwie einfach die Datei, bzw. das ViewModel als StaticRessource angeben?
    Donnerstag, 6. November 2014 12:31
  • Das geht natürlich auch:

    <Window x:Class....
            xmlns:vms="clr-namespace:DeineApp.ViewModel"
            .... />
    
    ...
    
    <Window.DataContext>
        <vms:MainViewModel />
    </Window.DataContext>
    
    ...
    
    <ProgressBar>
        <ProgressBar.DataContext>
            <vms:ProgessBarViewModel />
        </ProgressBar.DataContext>
    </ProgressBar>
    



    Andreas Richter
    Softwareentwickler und -architekt
    http://www.anrichter.net

    • Als Antwort markiert Marcel Gpunkt Donnerstag, 6. November 2014 13:06
    • Tag als Antwort aufgehoben Marcel Gpunkt Donnerstag, 6. November 2014 13:14
    • Als Antwort markiert Marcel Gpunkt Donnerstag, 6. November 2014 13:58
    Donnerstag, 6. November 2014 12:43
  • Ok, ich versuch es jetzt nochmal zusammenzufassen:

    Mein XAML:

    <Window x:Class="WpfVideowand.AdminConsole" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Converter="clr-namespace:WpfVideowand" xmlns:vms="clr-namespace:ASAVideowand.ProgressBarViewModel" Title="AdminConsole" Name="AdminConsoleWindow" WindowStartupLocation="Manual" Top="0" Left="0" Loaded="AdminConsole_Loaded" SizeToContent="WidthAndHeight" MaxHeight="1080" MaxWidth="1920" Closing="Window_Closing" WindowStyle="ThreeDBorderWindow" ResizeMode="CanMinimize">

    ...

              <ProgressBar HorizontalAlignment="Left" VerticalAlignment="Top" Height="25" Width="760" Name="MyProgressBar"
                           Value="{Binding ProgressValue}" Minimum="{Binding MinValue}" Maximum="{Binding MaxValue}">
                <ProgressBar.DataContext>
                  <vms:ProgressBarViewModel/>
                </ProgressBar.DataContext>
              </ProgressBar>

    Mein Code-Behind:

    namespace ASAVideowand.ProgressBarViewModel
    {
        class ProgressBarViewModel : INotifyPropertyChanged
        {
            private int _progressValue = 0;
            private int _maxValue = 100;
            private int _minValue = 0;
    
            public int ProgressValue 
            {
                get { return _progressValue; }
                set
                {
                    _progressValue = value;
                    OnPropertyChanged();
                } 
            }
    
            public int MaxValue
            {
                get { return _maxValue; }
                set
                {
                    _maxValue = value;
                    OnPropertyChanged();
                }
            }
    
            public int MinValue
            {
                get { return _minValue; }
                set
                {
                    _minValue = value;
                    OnPropertyChanged();
                }
            }
    
            public void RaiseValue(int raiseValue = 1)
            {
                ProgressValue += raiseValue;
            }
    
            public void SetMaximum(int maxValue)
            {
                MaxValue = maxValue;
            }
    
            public void SetMinimum(int minValue)
            {
                MinValue = minValue;
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                var handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }
    }
    

    Ich bekomme jetzt nur die Meldung dass der Name ProgressBarViewModel im Namespace ASAVideowand.ProgressBarViewModel nicht vorhanden wäre...

    Was habe ich vergessen?

    Donnerstag, 6. November 2014 13:14
  • Sieht auf den ersten Blick ok aus. Bis auf die Tatsache, dass der Namespace genauso heißt, wie dein ViewModel. Das könntest du mal ändern. Vielleicht verhaspelt sich WPF deswegen.

    Nur am Rande: Dein ViewModel ist nicht CodeBehind ;)


    Andreas Richter
    Softwareentwickler und -architekt
    http://www.anrichter.net

    Donnerstag, 6. November 2014 13:32
  • Ich habs geändert, bringt aber leider nichts.

    Das Lustige ist, dass, wenn ich "<vms:" eingebe, mir VS sogar vorschlägt "ProgressBarViewModel" zu nutzen.

    Da muss ja noch irgendwas an meinem ViewModel falsch sein, oder?


    p.s.: Es fehlte das public vor dem class ProgressBarViewModel... aber eine Änderung half auch nicht weiter.
    Donnerstag, 6. November 2014 13:38
  • Pack mal ein public vor class ProgressBarViewModel

    Andreas Richter
    Softwareentwickler und -architekt
    http://www.anrichter.net

    Donnerstag, 6. November 2014 13:49
  • Ändere mal

    xmlns:vms="clr-namespace:ASAVideowand.ProgressBarViewModel"


    in

    xmlns:vms="clr-namespace:ASAVideowand"

    Bringt das was ? Die Namespace-Deklaration braucht ja nur den Namespace selber und nicht das ViewModel, ads benutzt wird.

    Claudius

    • Als Antwort markiert Marcel Gpunkt Donnerstag, 6. November 2014 13:58
    Donnerstag, 6. November 2014 13:52
  • Erstmal, danke für die Sache mit dem ViewModel, Andreas und auch danke für das mit dem "Sub-Namespace" an Claudius. Das war es. :)
    Donnerstag, 6. November 2014 13:58