WPF Listview

Kevin Bell 21 Reputation points
2020-12-29T12:54:13.513+00:00

Hi,

I have a WPF ListView, the objects of which are bound to a view model and I use a CollectionViewSource to group them based on their properties - the CollectionViewSource dynamically creates expanders and groups the items in the ListView - this works fine.

I would like my routine to highlight certain items in the list (dependng on what the user does), to do this I've bound the background of the ListView item template - this also works fine. I then use ScrollIntoView to display the item.

But the issue I have is that oftern the Group expander is closed and the user cannot see the item, I'd like the routine to automatically expand the groups above when the item is highlighted.

As a demo I've created a app with a slider, when the slider is moved it highlights items in the ListView - but as you can see the Expanders remain closed unless the user expands them.

My code can be downloaded here:
view

The pertinent parts of the code are:

XAML that creates the ListView:

            <ListView Name="ListViewSheets"
                      Margin="10,10,10,10"
                      Height="314"
                      ItemsSource="{Binding BrowserItemCollectionView}">

                <ListView.ItemTemplate>
                    <DataTemplate>

                        <WrapPanel>
                            <TextBlock Text="{Binding Name}">
                                <TextBlock.Style>
                                    <Style TargetType="{x:Type TextBlock}">
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding Active}"
                                                         Value="true">
                                                <Setter Property="Background"
                                                        Value="YellowGreen" />
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </TextBlock.Style>
                            </TextBlock>
                        </WrapPanel>

                    </DataTemplate>
                </ListView.ItemTemplate>

                <ListView.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.ContainerStyle>
                            <Style TargetType="{x:Type GroupItem}">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate>
                                            <Expander>
                                                <Expander.Header>
                                                    <TextBlock Text="{Binding Name}"
                                                               FontWeight="Bold"
                                                               VerticalAlignment="Bottom" />
                                                </Expander.Header>
                                                <ItemsPresenter />
                                            </Expander>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </GroupStyle.ContainerStyle>
                    </GroupStyle>
                </ListView.GroupStyle>
            </ListView>

Code Behind MainWindow which creates the groups:

        public MainWindow()
        {
            InitializeComponent();

            store = new MainWindowViewModel();

            store.Browser.Add(new BrowserItem() { Name = "Level 01 Plan", L1group = "Architectural", Active = true, L2group = "Floor Plans", L3group = "1:100" }); ;
            store.Browser.Add(new BrowserItem() { Name = "Level 01 Plan", L1group = "Structural", Active = false, L2group = "Floor Plans", L3group = "1:20" });
            store.Browser.Add(new BrowserItem() { Name = "Level 02 Plan", L1group = "Architectural", Active = false, L2group = "Floor Plans", L3group = "1:100" });
            store.Browser.Add(new BrowserItem() { Name = "Level 02 Plan", L1group = "Structural", Active = false, L2group = "Floor Plans", L3group = "1:100" });
            store.Browser.Add(new BrowserItem() { Name = "Elevation East", L1group = "Architectural", Active = false, L2group = "Elevations", L3group = "1:100" });
            store.Browser.Add(new BrowserItem() { Name = "Elevation West", L1group = "Architectural", Active = false, L2group = "Elevations", L3group = "1:20" });
            store.Browser.Add(new BrowserItem() { Name = "Elevation North", L1group = "Architectural", Active = false, L2group = "Elevations", L3group = "1:100" });
            store.Browser.Add(new BrowserItem() { Name = "Section A-A", L1group = "Architectural", Active = false, L2group = "Sections", L3group = "1:100" });
            store.Browser.Add(new BrowserItem() { Name = "Section B-B", L1group = "Architectural", Active = false, L2group = "Sections", L3group = "1:100" });
            store.Browser.Add(new BrowserItem() { Name = "Section C-C", L1group = "Structural", Active = false, L2group = "Sections", L3group = "1:100" });
            store.Browser.Add(new BrowserItem() { Name = "Main Section", L1group = "Structural", Active = false, L2group = "Main Sections", L3group = "1:100" });
            store.Browser.Add(new BrowserItem() { Name = "Level 01 RCP", L1group = "Architectural", Active = false, L2group = "Ceiling Plans", L3group = "1:100" });

            ListViewSheets.DataContext = store;

            FilterBoxText.DataContext = store;

            //Set slider max value
            changeactive.Maximum = store.Browser.Count() - 1;
        }

This is MainWindowViewModel class, it has the ObservableCollection which stores the BrowserItems, and a constructor which sets up the grouping for the ListView using a CollectionViewSource:

class MainWindowViewModel : ViewModelBase
{
    public ICollectionView BrowserItemCollectionView { get; }

    private ObservableCollection<BrowserItem> _browser = new ObservableCollection<BrowserItem>();

    public ObservableCollection<BrowserItem> Browser
    {
        get => _browser;
        set
        {
            if (value != _browser)
            {
                _browser = value;
                OnPropertyChanged(nameof(Browser));
            }
        }
    }

    private string _browserItemFilter = string.Empty;
    public string BrowserItemFilter
    {
        get
        {
            return _browserItemFilter;
        }
        set
        {
            _browserItemFilter = value;
            OnPropertyChanged(nameof(BrowserItemFilter));
            BrowserItemCollectionView.Refresh();
        }
    }

    public MainWindowViewModel()
    //Constructor
    {
        BrowserItemCollectionView = CollectionViewSource.GetDefaultView(_browser);

        //Set up filter
        BrowserItemCollectionView.Filter = FilterBrowserItems;

        //Set up grouping
        BrowserItemCollectionView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(BrowserItem.L1group)));
        BrowserItemCollectionView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(BrowserItem.L2group)));
        BrowserItemCollectionView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(BrowserItem.L3group)));
    }

    private bool FilterBrowserItems(object obj)
    {
        if (obj is BrowserItem check)
        {
            return check.Name.Contains(BrowserItemFilter);
        }

        return false;
    }
}

The slider changed value event which highlights the item in the ListView (by setting its Active property to True):

        private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            //When slider is moved, set the item in the list as active
            for (int f = 0; f < store.Browser.Count; f++)
            {
                if (f == changeactive.Value)
                {
                    store.Browser[f].Active = true;
                    displayslider.Text = f.ToString();
                    ListViewSheets.ScrollIntoView(store.Browser[f]);
                }
                else
                {
                    store.Browser[f].Active = false;
                }
            }
        }

The BrowserItem class that represents an item in the ListView has a property for Active which is bound to the highlight, a Name property and three properties which represent the grouping (L1group, L2group and L3 group).

Any help or pointers would be very much appriciated.
Thanks.

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,685 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
768 questions
{count} votes