none
WPF gruppierte ListView mit Expander. Expander des SelectedItem öfnen.

    Frage

  • Hi,
    ich habe in einer WPF Anwendung (C#, MVVMLight) eine ListView. Die Daten kommen per Binding aus einer ObservableCollection<Model>. Model ist in dem Fall eine Klasse mit mehreren Properties. Die Daten werden gruppiert angezeigt.

    <CollectionViewSource x:Key="src" Source="{Binding MCollection, Mode=OneWay}">
    	<CollectionViewSource.GroupDescriptions>
    		<PropertyGroupDescription PropertyName="MGroup" />
    	</CollectionViewSource.GroupDescriptions>
    	<CollectionViewSource.LiveFilteringProperties>
    		
    	</CollectionViewSource.LiveFilteringProperties>
    </CollectionViewSource>
    
    
    <ListView.GroupStyle>
    	<GroupStyle>
    		<GroupStyle.ContainerStyle>
    			<Style TargetType="{x:Type GroupItem}">
    				<Setter Property="Template">
    					<Setter.Value>
    						<ControlTemplate>
    							<Expander IsExpanded=
    Beim Start der Anwedung wird das SelectedItem vorbelegt. Wie kann ich sämtliche Expander schließen, bis auf dir Gruppe, in der das SelectedItem ist?

    Im Netz habe ich das gefunden, bekomme es aber nicht umgesetzt. Hat jemand eine Lösung/einen Tipp?

    Danke
    Gruß Stefan


    Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP


    • Bearbeitet Stefan Krömer Freitag, 14. September 2018 19:12 Link vergessen
    Freitag, 14. September 2018 16:55

Antworten

  • Hi Stefan,
    Du kannst die IsSelected-Eigenschaft binden und über die Bindung auswerten, ob der betreffende Expander zu öffnen ist. Nachfolgend eine Demo. Ob sie bei Dir passt, kann ich ohne Kenntnisse der weiteren Umgebung/Anforderungen nicht sagen.

    XAML:

    <Window x:Class="WpfApp1.Window71"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            mc:Ignorable="d"
            Title="Window71" Height="450" Width="800">
      <Window.Resources>
        <local:Window71VM x:Key="vm"/>
        <local:Window71Conv x:Key="conv"/>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <ListView ItemsSource="{Binding View}" SelectedItem="{Binding SelectedItem}" >
          <ListView.GroupStyle>
            <GroupStyle>
              <GroupStyle.ContainerStyle>
                <Style TargetType="{x:Type GroupItem}">
                  <Setter Property="Template">
                    <Setter.Value>
                      <ControlTemplate>
                        <Expander>
                          <Expander.Header>
                            <StackPanel Orientation="Horizontal">
                              <TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
                              <TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Green" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
                              <TextBlock Text=" item(s)" FontSize="22" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
                            </StackPanel>
                          </Expander.Header>
                          <Expander.IsExpanded>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource conv}">
                              <Binding Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                              <Binding
                    RelativeSource="{RelativeSource FindAncestor, AncestorType=ListView, AncestorLevel=1}"
                    Path="DataContext.SelectedItem"></Binding>
                            </MultiBinding>
                          </Expander.IsExpanded>
                          <ItemsPresenter />
                        </Expander>
                      </ControlTemplate>
                    </Setter.Value>
                  </Setter>
                </Style>
              </GroupStyle.ContainerStyle>
            </GroupStyle>
          </ListView.GroupStyle>
          <ListView.View>
            <GridView>
              <GridViewColumn Header="ID" Width="30" DisplayMemberBinding="{Binding ID}" />
              <GridViewColumn Header="Info" Width="200" DisplayMemberBinding="{Binding Info}" />
            </GridView>
          </ListView.View>
        </ListView>
      </Grid>
    </Window>

    Dazu der Code:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Data;
    
    namespace WpfApp1
    {
      public class Window71VM
      {
        private CollectionViewSource cvs = new CollectionViewSource();
        public ICollectionView View
        {
          get
          {
            if (cvs.Source == null)
            {
              var col = GetData();
              cvs.Source = col;
              cvs.View.GroupDescriptions.Add(new PropertyGroupDescription("MGroup"));
            }
            return cvs.View;
          }
        }
    
        public object SelectedItem { get; set; }
    
        private ObservableCollection<Window71Data> GetData()
        {
          ObservableCollection<Window71Data> liste = new ObservableCollection<Window71Data>();
          for (int i = 1; i < 11; i++) liste.Add(new Window71Data() { ID = i, Info = $"Zeile {i}", MGroup = $"Gruppe {i % 3}" });
          // für Demo-Zwecke ein Element als Selected setzen
          SelectedItem = liste[4];
          return liste;
        }
      }
      public class Window71Data
      {
        public int ID { get; set; }
        public string Info { get; set; }
        public string MGroup { get; set; }
      }
      public class Window71Conv : IMultiValueConverter
      {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
          foreach (Window71Data item in ((CollectionViewGroup)values[0]).Items) if (item == values[1]) return true;
          return false;
        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>  throw new NotImplementedException();
      }
    }


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort vorgeschlagen Peter Fleischer Samstag, 15. September 2018 03:13
    • Als Antwort markiert Stefan Krömer Samstag, 15. September 2018 07:26
    Samstag, 15. September 2018 03:12

Alle Antworten

  • Hi Stefan,
    Du kannst die IsSelected-Eigenschaft binden und über die Bindung auswerten, ob der betreffende Expander zu öffnen ist. Nachfolgend eine Demo. Ob sie bei Dir passt, kann ich ohne Kenntnisse der weiteren Umgebung/Anforderungen nicht sagen.

    XAML:

    <Window x:Class="WpfApp1.Window71"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            mc:Ignorable="d"
            Title="Window71" Height="450" Width="800">
      <Window.Resources>
        <local:Window71VM x:Key="vm"/>
        <local:Window71Conv x:Key="conv"/>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <ListView ItemsSource="{Binding View}" SelectedItem="{Binding SelectedItem}" >
          <ListView.GroupStyle>
            <GroupStyle>
              <GroupStyle.ContainerStyle>
                <Style TargetType="{x:Type GroupItem}">
                  <Setter Property="Template">
                    <Setter.Value>
                      <ControlTemplate>
                        <Expander>
                          <Expander.Header>
                            <StackPanel Orientation="Horizontal">
                              <TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
                              <TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Green" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
                              <TextBlock Text=" item(s)" FontSize="22" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
                            </StackPanel>
                          </Expander.Header>
                          <Expander.IsExpanded>
                            <MultiBinding Mode="OneWay" Converter="{StaticResource conv}">
                              <Binding Path="DataContext" RelativeSource="{RelativeSource Self}"/>
                              <Binding
                    RelativeSource="{RelativeSource FindAncestor, AncestorType=ListView, AncestorLevel=1}"
                    Path="DataContext.SelectedItem"></Binding>
                            </MultiBinding>
                          </Expander.IsExpanded>
                          <ItemsPresenter />
                        </Expander>
                      </ControlTemplate>
                    </Setter.Value>
                  </Setter>
                </Style>
              </GroupStyle.ContainerStyle>
            </GroupStyle>
          </ListView.GroupStyle>
          <ListView.View>
            <GridView>
              <GridViewColumn Header="ID" Width="30" DisplayMemberBinding="{Binding ID}" />
              <GridViewColumn Header="Info" Width="200" DisplayMemberBinding="{Binding Info}" />
            </GridView>
          </ListView.View>
        </ListView>
      </Grid>
    </Window>

    Dazu der Code:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Data;
    
    namespace WpfApp1
    {
      public class Window71VM
      {
        private CollectionViewSource cvs = new CollectionViewSource();
        public ICollectionView View
        {
          get
          {
            if (cvs.Source == null)
            {
              var col = GetData();
              cvs.Source = col;
              cvs.View.GroupDescriptions.Add(new PropertyGroupDescription("MGroup"));
            }
            return cvs.View;
          }
        }
    
        public object SelectedItem { get; set; }
    
        private ObservableCollection<Window71Data> GetData()
        {
          ObservableCollection<Window71Data> liste = new ObservableCollection<Window71Data>();
          for (int i = 1; i < 11; i++) liste.Add(new Window71Data() { ID = i, Info = $"Zeile {i}", MGroup = $"Gruppe {i % 3}" });
          // für Demo-Zwecke ein Element als Selected setzen
          SelectedItem = liste[4];
          return liste;
        }
      }
      public class Window71Data
      {
        public int ID { get; set; }
        public string Info { get; set; }
        public string MGroup { get; set; }
      }
      public class Window71Conv : IMultiValueConverter
      {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
          foreach (Window71Data item in ((CollectionViewGroup)values[0]).Items) if (item == values[1]) return true;
          return false;
        }
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>  throw new NotImplementedException();
      }
    }


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort vorgeschlagen Peter Fleischer Samstag, 15. September 2018 03:13
    • Als Antwort markiert Stefan Krömer Samstag, 15. September 2018 07:26
    Samstag, 15. September 2018 03:12
  • Hallo Peter,

    vielen Dank!!!

    Ich denke einfach zu kompliziert...

    Das selektierte Item war mir ja schlüssig..

    <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=ListView, AncestorLevel=1}" Path="DataContext.SelectedItem"/>

    ...aber das..

    <Binding Path="DataContext" RelativeSource="{RelativeSource Self}"/>

    ..sieht eigentlich relativ einfach aus. Man muss es halt nur wissen.

    Danke, Gruß


    Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP

    Samstag, 15. September 2018 07:26
  • Hi Stefan,
    der Converter soll prüfen, ob sich das selektierte Element in der Menge der Elemente der Gruppe befindet. Deshalb ist es logisch und nicht kompliziert, dass der Converter sowohl die Gruppe als auch das selektierte Element benötigt. Da man die ConverterParameter nicht binden kann (keine DependencyProperty) benötigt man einen MultiConverter. Im ersten Parameter wird die Gruppe übergeben, die man mit Self bekommt und im zweiten Parameter wird das selektierte Element übergeben. Im Beispiel habe ich mir das Element aus dem ViewModel geholt (über den DataContext der ListView). Man kann es aber auch anders machen, z.B. aus der Eigenschaft des ListView. Zusätzlich sollte bei Bedarf auch noch auf null getestet werden, d.h., wenn nichts selektiert wurde (z.B. am Anfang).

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks


    Samstag, 15. September 2018 09:39
  • Hi Peter,

    bei der Gruppe habe ich völlig daneben gedacht. Vielen Dank nochmal für deine Lösungen und Erklärungen.

    Grruß


    Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP

    Samstag, 15. September 2018 11:22