I've writing an app that displays a list of drawings, as a test I've written a small app that displays how I envisage it working.
The app works by:
Creating a BrowserItem class, this has a name property, active property and three levels of grouping.
Has a WPF ListView which is bound to an ObservableCollection, and uses an ICollectionView to display the BrowserItems in the list view nested based on the three levels of grouping
When the app runs, it creates several BrowserItems, sets their names and grouping etc and displays the items in the ListView
This all works well.
As a test the app has a slider which depending on the slider number simply set the Active property of a BrowserItem to true - this causes it to be highlighted in green in the list view as Active is a bound property to the font background.
What I would like to happen is when its highlighted in green, the routine will automatically expand all Expanders so the user can see the highlighted item without having to hunt for it.
Is there any way I can get the Browser items parent expander?
The code can be downloaded here: https://drive.google.com/file/d/1MW-v2gqFtubhnZdKUYGglQLN3S5xnVB4/view?usp=sharing
BrowserItem Class:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ListBoxTest3
{
public partial class BrowserItem : ViewModelBase
{
/// <summary>
/// BrowserItem represents a node on the browser treeview structure and relates to either a browser branch or a view / sheet
/// </summary>
//Group one nesting level
private string _l1group;
public string L1group
{
get
{
return _l1group;
}
set
{
_l1group = value;
OnPropertyChanged(nameof(L1group));
}
}
//Group two nesting level
private string _l2group;
public string L2group
{
get
{
return _l2group;
}
set
{
_l2group = value;
OnPropertyChanged(nameof(L2group));
}
}
//Group three nesting level
private string _l3group;
public string L3group
{
get
{
return _l3group;
}
set
{
_l3group = value;
OnPropertyChanged(nameof(L3group));
}
}
//Name
private string _name;
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
}
}
}
//Active or _active = is bound to the active view highlight
private bool _active;
public bool Active
{
get => _active;
set
{
if (value != _active)
{
_active = value;
OnPropertyChanged(nameof(Active));
}
}
}
}
}
MainWindow.Xaml:
<Window x:Class="ListBoxTest3.MainWindow"
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:ListBoxTest3"
mc:Ignorable="d"
Title="MainWindow"
Height="450"
Width="400">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal"
Margin="10">
<TextBlock Text="Filter: " />
<TextBox x:Name="FilterBoxText"
Width="330"
Text="{Binding BrowserItemFilter, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal"
Margin="10">
<TextBlock Text="Change Active to number: " />
<Slider x:Name="changeactive"
Width="210"
SmallChange="1"
ValueChanged="Slider_ValueChanged"
IsSnapToTickEnabled="True"
TickFrequency="1"
TickPlacement="Both">
</Slider>
<TextBlock x:Name="displayslider"
Text="0" />
</StackPanel>
</StackPanel>
</StackPanel>
<ListView Name="TreeViewSheets"
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>
</StackPanel>
</Grid>
</Window>
Code behind MainWindow.xaml:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace ListBoxTest3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
MainWindowViewModel store
{
get;
set;
}
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" });
TreeViewSheets.DataContext = store;
FilterBoxText.DataContext = store;
//Set slider max value
changeactive.Maximum = store.Browser.Count() - 1;
}
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();
TreeViewSheets.ScrollIntoView(store.Browser[f]);
}
else
{
store.Browser[f].Active = false;
}
}
}
}
}
MainWindowViewModel class, this contains the ObservableCollection that contains the browser items. It also has a constructor for MainWindowViewModel that groups using a CollectionViewSource - this dynamically creates the group expanders for the Listview based on the L1Group, L2Group and L3Group fields of the BrowserItem class:
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;
}
}
You can name the
Expanderand then look it up in the template of the group item by its name or use theVisualTreeHelperto traverse the visual tree to find theExpander:Find and expand all
Expanderelements in groupThe following version returns all
Expanderelements in the visual tree starting from the selected group item: