CollectionViewSource WPF



  • Hallo zusammen.

    Eine Frage zur CollectionViewSource in WPF

    Habe in meinem ViewModel eine CollectionViewSource. Deren Source verweist auf eine ObservableCollection<MyType> die auch im ViewModel enthalten ist.

    Auf meiner View binde ich nun eine Listbox an diese CollectionViewSource.

    Profiles heißt die CollectionViewSource

    <ListBox Height="200"
                    DisplayMemberPath="Name"
                    ItemsSource="{Binding Profiles.View}" />
    

    Nun habe ich noch eine ListView auf meiner View. Hier würde ich ebenfallas gerne auf die CollectionViewSource verweisen. Allerdings auf eine Liste die wiederum in meiner darunterliegenden Klasse also MyType enhalten ist. Also soll in der ListView die Liste des aktuell angewählten Objekts der Listbox angezeigt werden. Machines heißt die Liste in MyType.

    Habe das mal so getestet.

    <ListView Height="100"
                      DisplayMemberPath="Machines"
                      ItemsSource="{Binding Profiles.View}">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Width="150"
                                        DisplayMemberBinding="{Binding Name}"
                                        Header="Name" />
                        <GridViewColumn Width="50"
                                        DisplayMemberBinding="{Binding Type}"
                                        Header="Typ" />
                    </GridView>
                </ListView.View>
            </ListView>
    

    Nun wird aber in der Listview das selbe angezeigt wie in der Listbox.

    Was mache ich falsch?



  • Mach ein Binding auf das "SelectedItem" in deiner ListBox und dann setz den ItemsSource auf die Collection in deiner Klasse


  • Administrator

    Dein Binding ist ja beides Mal auf die gleiche Collection. Du kannst das aber ganz einfach lösen. Füge dem zweiten Binding noch ein / an. Damit bindest du immer das CurrentItem der View .

    Also:

    <ListBox Height="200"
             DisplayMemberPath="Name"
             ItemsSource="{Binding Profiles.View}"
             IsSynchronizedWithCurrentItem="True" />
    
    <ListView Height="100"
              DisplayMemberPath="Machines"
              ItemsSource="{Binding Profiles.View/}" >
    <ListView.View>
      <GridView>
        <GridViewColumn Width="150"
                        DisplayMemberBinding="{Binding Name}"
                        Header="Name" />
        <GridViewColumn Width="50"
                        DisplayMemberBinding="{Binding Type}"
                        Header="Typ" />
      </GridView>
    </ListView.View>
    </ListView>
    

    Das IsSynchronizedWithCurrentItem="True" ist nur zur Sicherheit, sollte auch ohne gehen.

    Grüssli



  • Hallo Dravere

    Habe deinen Code getestet. Leider wird nun in der Listview gar nichts mehr angezeigt.

    Habe hier eine funktionierende Lösung:

    <ListBox Height="200"
                     DisplayMemberPath="Name"
                     ItemsSource="{Binding Profiles.View}" />
    
            <ListView Height="100"
                      DataContext="{Binding Profiles.View}"
                      ItemsSource="{Binding Machines}">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Width="150"
                                        DisplayMemberBinding="{Binding Name}"
                                        Header="Name" />
                        <GridViewColumn Width="50"
                                        DisplayMemberBinding="{Binding Type}"
                                        Header="Typ" />
                        <GridViewColumn Width="50"
                                        DisplayMemberBinding="{Binding Build}"
                                        Header="Bauart" />
                        <GridViewColumn Width="50"
                                        DisplayMemberBinding="{Binding No}"
                                        Header="Nummer" />
                    </GridView>
                </ListView.View>
            </ListView>
        </StackPanel>
    

    Bin hier über den Datencontext gegangen. Ob das auch noch anders geht und wann man Datacontext überhaupt verwenden soll weiß ich nicht genau.


  • Administrator

    @braunbär,
    Ah, jetzt verstehe ich überhaupt erst, was du ursprünglich erreichen wolltest. Daher dieses DisplayMemberPath im ListView . Hatte mich noch über dieses Property gewundert, welches von ListView ja gar nicht berücksichtig wird. Du willst also in der ListView auf das Property Machines des selektierten Objektes der ListBox binden?

    Dann musst du nicht ein / hinzufügen, sondern ein /Machines 🙂

    <ListBox Height="200"
             DisplayMemberPath="Name"
             ItemsSource="{Binding Profiles.View}"
             IsSynchronizedWithCurrentItem="True" />
    
    <ListView Height="100"
              ItemsSource="{Binding Profiles.View/Machines}" >
    <ListView.View>
      <GridView>
        <GridViewColumn Width="150"
                        DisplayMemberBinding="{Binding Name}"
                        Header="Name" />
        <GridViewColumn Width="50"
                        DisplayMemberBinding="{Binding Type}"
                        Header="Typ" />
      </GridView>
    </ListView.View>
    </ListView>
    

    Siehe dazu: http://msdn.microsoft.com/en-us/library/ms752347.aspx#current_record_pointers

    Grüssli



  • Na das ist mal interessant. Das wußte ich bisher auch noch nicht. Dank dir.


  • Administrator

    braunbär schrieb:

    Na das ist mal interessant. Das wußte ich bisher auch noch nicht.

    Ich habe es auch erst relativ spät mitbekommen. Irgendwie stand das nie in einem Tutorial oder ähnliches und ich treffe es auch heute noch selten an, dabei ist es äusserst praktisch.

    Ähnlich wie auch diese nette Funktion:
    http://msdn.microsoft.com/en-us/library/system.windows.data.collectionviewsource.getdefaultview.aspx

    Obwohl man eine ObservableCollection an die View übergibt, kommt man trotzdem an das ICollectionView ran und somit auch an das selektierte Item, bzw. den entsprechenden Events. Man kann auch das selektierte Item damit setzen ( MoveCurrentToXXX ). Das ganze lässt sich dann auch prima in Unit Tests integrieren.

    Grüssli



  • Hallo nochmal

    Da hätte ich gleich noch eine Frage zum CurrentItem einer CollectionViewSource.
    Habe hier ein seltsames Verhalten.

    Im XAML funktioniert das mit dem CurrentItem, allerdings habe ich im Code ein Problem. Greife ich auf das CurrentItem in der View zu erhalte ich egal was in der ListView ausgewählt ist das erste Objekt in der List. Das allerdings nur beim ersten Mal.

    Wähle ich dann erneut einen Eintrag aus der ListView aus steht in CurrentItem das richtige drin.

    Was läuft da schief.



  • Das mit dem GetDefaultView habe ich auch nicht ganz verstanden.

    Wenn ich doch das CurrentItem will gehe ich doch einfach über .View.CurrentItem

    Wie wende ich GetDefaultView() an. Und was übergebe ich als Parameter. Das ist für mich noch verwirrend.



  • 1. Sorry nicht ListView sondern ListBox
    2. Ok das mit dem GetDefaultView ist mir nun klar
    Aber das Problem besteht auch hier in CurrentItem steht nicht drin was in der Liste ausgewählt wurde.


  • Administrator

    Hast du IsSynchronizedWithCurrentItem auf True gesetzt?

    Grüssli



  • Ja habe ich.

    Habe nun mal ein ganz einfaches Projekt gemacht.

    Eine ObservableCollection mit String. Diese habe ich der Source einer CollectionViewSource zugewiesen. Diese wiederum an eine ListView gebunden.

    Über einen Button lese ich auch hier nun das CurrentItem aus. Aber auch hier wird beim ersten mal immer der erste Eintrag angezeigt, egal was ausgewählt ist.

    Auch in der Liste wird nach Abfrage des CurrentItem der erste Wert selektiert.

    Verstehe ich nicht. Wie kann eine Abfrage eines Objekts den das Objekt ändern.


  • Administrator

    braunbär schrieb:

    Eine ObservableCollection mit String. Diese habe ich der Source einer CollectionViewSource zugewiesen. Diese wiederum an eine ListView gebunden.

    Hast du auch mal die CollectionViewSource ausgelassen? Wozu brauchst du diese überhaupt und wo erstellst du sie? XAML oder Code?

    Kannst du dein kurzes Beispiel hier präsentieren? Dann können wir ebenfalls damit testen. Oder sehen allenfalls gleich den Fehler 🙂

    Grüssli



  • So das ganz einfache Beispiel

    public partial class MainWindow : Window
    {
        public ObservableCollection<string> InternalHouses { get; set; }
    
        public CollectionViewSource Houses { get; set; } 
    
        public MainWindow()
        {
            InitializeComponent();
    
            DataContext = this;
    
            InternalHouses = new ObservableCollection<string>();
    
            InternalHouses.Add("house1");
            InternalHouses.Add("house2");           
            InternalHouses.Add("house3");
    
            Houses = new CollectionViewSource { Source = InternalHouses };
        }
    
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var house = (string)Houses.View.CurrentItem;
        }
    }
    
    <StackPanel>
    
            <Button Click="Button_Click">Actual House</Button>
    
            <Label>Houses:</Label>
    
            <ListView Height="200"
                      IsSynchronizedWithCurrentItem="True"
                      ItemsSource="{Binding Houses.View}" />
    
        </StackPanel>
    

    In der ListView ist nun das erste Objekt ausgewählt. Wähle ich nun ein anderes Objekt aus. z.B. "House 2" und klicke dann den Button steht in CurrentItem "House 1" und in der ListView wird auch "House 1" wieder ausgewählt. Wähle ich nun nochmal "House 2" aus und klicke den Button steht in CurrentItem auch "House 2" drin


  • Administrator

    So funktioniert es:

    public partial class MainWindow : Window
    {
      public ObservableCollection<string> Houses { get; set; }
    
      public MainWindow()
      {
        InitializeComponent();
    
        DataContext = this;
    
        Houses = new ObservableCollection<string>();
    
        Houses.Add("house1");
        Houses.Add("house2");
        Houses.Add("house3");
      }
    
      private void Button_Click(object sender, RoutedEventArgs e)
      {
        var house = (string)CollectionViewSource.GetDefaultView(this.Houses).CurrentItem;
      }
    }
    

    Ich bin mir aktuell nur noch nicht ganz klar, wieso dem so ist, und werde mal danach suchen. Allerdings habe ich selber noch nie eine CollectionViewSource direkt im Code erstellt. Ich sehe keinerlei Vorteil darin. Überlass dies doch WPF.

    Grüssli



  • Also das ganze kommt von hier:

    http://www.xamlplayground.org/post/2009/07/18/Use-CollectionViewSource-effectively-in-MVVM-applications.aspx

    Wenn ich in meinem Code das CurrentItem, direkt nachdem ich die CollectionViewsource erstellt habe, einmal auslese. Dann funktioniert das nachher mit dem CurrentItem im Buttonhandler auch.
    Oder ich kann es auch mit MoveCurrentTo erstmal setzen dann funktioniert es auch.

    Meiner Meinung ist das ein Bug.


  • Administrator

    braunbär schrieb:

    Also das ganze kommt von hier:
    http://www.xamlplayground.org/post/2009/07/18/Use-CollectionViewSource-effectively-in-MVVM-applications.aspx

    Ich würde mal sagen, dem Mann war nicht bekannt, dass es die Funktion CollectionViewSource.GetDefaultView gibt. WPF erstellt automatisch eine CollectionViewSource , wenn man eine Liste an ein Control bindet. Es wird nie z.B. direkt die OberservableCollection angebunden.

    Daher kann man die ObservableCollection binden und falls man an die dazugehörige ICollectionView kommen möchte, kann man CollectionViewSource.GetDefaultView aufrufen.

    Grüssli



  • Achso die CollectionViewSource wird automatisch erstellt. Dachte ich muss die im XAML selber erstellen. Ja dann macht mir auch GetDefaultView sehr viel Sinn. Vielen Dank.

    Das andere ist meiner Meinung aber trotzdem ein bug 😉


Log in to reply