解决 筛选后关闭列表框扩展器

456

成员
已加入
2020年4月19日
留言内容
6
编程经验
1-3
你好,

我有一个小例程,带有带有文本框的ListView-ListView绑定到视图模型。我使用CollectionView对列表框中的项目进行分组-文本框运行过滤器以根据输入的内容隐藏项目。

这一切都非常好-除非键入文本框并运行过滤器,否则所有由CollectionView动态创建的扩展器都将关闭。

这是由于必须使用ListView.Refresh()更新并显示过滤后的ListView引起的-Refresh()关闭所有扩展器。

有什么办法解决这个问题,我无法相信这是标准的操作方式,因为每次键入内容时都会关闭扩展器,这确实令人不快。

我将不胜感激。


这是'Dog'ListView中显示的对象:
    public partial class Dogs : ViewModelBase
    {

        //Group one nesting level
        private string _l1grouping;
        public string L1grouping
        {
            get
            {
                return _l1grouping;
            }
            set
            {
                _l1grouping = value;
                OnPropertyChanged(nameof(L1grouping));
            }
        }

        //Group two nesting level
        private string _l2grouping;
        public string L2grouping
        {
            get
            {
                return _l2grouping;
            }
            set
            {
                _l2grouping = value;
                OnPropertyChanged(nameof(L2grouping));
            }
        }

        //Group three nesting level
        private string _l3grouping;
        public string L3grouping
        {
            get
            {
                return _l3grouping;
            }
            set
            {
                _l3grouping = value;
                OnPropertyChanged(nameof(L3grouping));
            }
        }

        //Name
        private string _name;
        public string Name
        {
            get => _name;
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged(nameof(Name));
                }
            }
        }
    }


这是View模型,用于将CollectionViewSource设置为动态创建Expanders和filter:
    class MainWindowViewModel : ViewModelBase
    {
        public ICollectionView DogsCollectionView { get; }

        private ObservableCollection<Dogs> _dogs = new ObservableCollection<Dogs>();

        public ObservableCollection<Dogs> Dogs
        {
            get => _dogs;
            set
            {
                if (value != _dogs)
                {
                    _dogs = value;
                    OnPropertyChanged(nameof(Dogs));
                }
            }
        }

        private string _dogFilter = string.Empty;
        public string DogFilter
        {
            get
            {
                return _dogFilter;
            }
            set
            {
                _dogFilter = value;
                OnPropertyChanged(nameof(DogFilter));
                DogsCollectionView.Refresh();
            }
        }

        public MainWindowViewModel()
        //Constructor
        {
            DogsCollectionView = CollectionViewSource.GetDefaultView(_dogs);

            //Set up filter
            DogsCollectionView.Filter = FilterBrowserItems;

            //Set up grouping
            DogsCollectionView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(SaveExpanderState.Dogs.L1grouping)));
            DogsCollectionView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(SaveExpanderState.Dogs.L2grouping)));
            DogsCollectionView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(SaveExpanderState.Dogs.L3grouping)));
        }

        private bool FilterBrowserItems(object obj)
        {
            if (obj is Dogs check)
            {
                string nametocheck = check.Name.ToLower();
                return nametocheck.Contains(DogFilter.ToLower());
            }

            return false;
        }
    }


创建狗的MainWindow代码:
   public partial class MainWindow : Window
    {
        MainWindowViewModel DogViewModel
        {
            get;
            set;
        }

        public MainWindow()
        {
            InitializeComponent();

            DogViewModel = new MainWindowViewModel();

            DogViewModel.Dogs.Add(new Dogs() { Name = "Max", L1grouping = "Large",  L2grouping = "Brown", L3grouping = "Fast" }); ;
            DogViewModel.Dogs.Add(new Dogs() { Name = "Bob", L1grouping = "Small", L2grouping = "Brown", L3grouping = "Slow" });
            DogViewModel.Dogs.Add(new Dogs() { Name = "Fido", L1grouping = "Large", L2grouping = "Brown", L3grouping = "Fast" });
            DogViewModel.Dogs.Add(new Dogs() { Name = "Brian", L1grouping = "Small", L2grouping = "Brown", L3grouping = "Fast" });
            DogViewModel.Dogs.Add(new Dogs() { Name = "Steve", L1grouping = "Large", L2grouping = "Black", L3grouping = "Fast" });
            DogViewModel.Dogs.Add(new Dogs() { Name = "Emma", L1grouping = "Large", L2grouping = "Black", L3grouping = "Slow" });
            DogViewModel.Dogs.Add(new Dogs() { Name = "Shep", L1grouping = "Large", L2grouping = "Black", L3grouping = "Fast" });
            DogViewModel.Dogs.Add(new Dogs() { Name = "Lassy", L1grouping = "Large", L2grouping = "White", L3grouping = "Fast" });
            DogViewModel.Dogs.Add(new Dogs() { Name = "Burt", L1grouping = "Large", L2grouping = "White", L3grouping = "Fast" });
            DogViewModel.Dogs.Add(new Dogs() { Name = "Siggy", L1grouping = "Small", L2grouping = "White", L3grouping = "Fast" });
            DogViewModel.Dogs.Add(new Dogs() { Name = "Heidi", L1grouping = "Small", L2grouping = "Black and White", L3grouping = "Fast" });
            DogViewModel.Dogs.Add(new Dogs() { Name = "Loki", L1grouping = "Large", L2grouping = "Yellow", L3grouping = "Fast" });

            ListViewDogs.DataContext = DogViewModel;

            FilterBoxText.DataContext = DogViewModel;
        }
    }
 
Solution
此解决方案似乎更容易: 如何在列表视图的组标题中保存IsExpanded状态-采取2
您可以附加到扩展器上的Loaded和Expanded / Collapsed事件。
C#:
private Dictionary<string, bool> expandStates = new Dictionary<string, bool>();

private void Expander_Loaded(object sender, RoutedEventArgs e)
{
    var expander = (Expander)sender;
    var dc = (CollectionViewGroup)expander.DataContext;
    var groupName = dc.Name.ToString();
    if (expandStates.TryGetValue(groupName, out var value))
        expander.IsExpanded = value;           
}

private void...

约翰·H

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
1,048
地点
挪威
编程经验
10+
此解决方案似乎更容易: 如何在列表视图的组标题中保存IsExpanded状态-采取2
您可以附加到扩展器上的Loaded和Expanded / Collapsed事件。
C#:
private Dictionary<string, bool> expandStates = new Dictionary<string, bool>();

private void Expander_Loaded(object sender, RoutedEventArgs e)
{
    var expander = (Expander)sender;
    var dc = (CollectionViewGroup)expander.DataContext;
    var groupName = dc.Name.ToString();
    if (expandStates.TryGetValue(groupName, out var value))
        expander.IsExpanded = value;           
}

private void Expander_ExpandedCollapsed(object sender, RoutedEventArgs e)
{
    var expander = (Expander)sender;
    var dc = (CollectionViewGroup)expander.DataContext;
    var groupName = dc.Name.ToString();
    expandStates[groupName] = expander.IsExpanded;
}
 
解决方案

跳伞

工作人员
已加入
2019年4月6日
留言内容
2,529
地点
弗吉尼亚州切萨皮克
编程经验
10+
有什么办法解决这个问题,我无法相信这是标准的操作方式,因为每次键入内容时都会关闭扩展器,这确实令人不快。
您必须记得,当WPF和Silverlight被设想为Project Avalon(2000年代初期)时,UI"language"当时不包括我们现在的行为,即在搜索框中键入动态更新搜索结果。然后,您键入搜索或过滤器,然后按Enter键以查看结果。当时的自动完成功能是最接近我们现在的功能,当时,自动完成功能被认为是一种用户帮助,可以帮助他们记住以前所做的事情,只是开始以具有大语料的Intellisense方式使用寻找潜在的比赛。
 

456

成员
已加入
2020年4月19日
留言内容
6
编程经验
1-3
感谢您的回复。 @约翰·H 我一直在尝试与您在下面的代码中指出的解决方案相似的解决方案。

我可以很容易地保存状态,但是恢复状态是一个问题,因为当我尝试让扩展器重新扩展它们时,它只会返回前两个而不是较低的级别(我认为这与延迟加载有关?)

保存扩展器状态的代码:
        public static Dictionary<string, bool> ExpanderState
        {
            get;
            set;
        }
       
        private void Expander_CollapsedorExpand(object sender, RoutedEventArgs e)
        {
            Expander thisExpander = sender as Expander;

            TextBlock HeaderTextBlock = thisExpander.Header as TextBlock;

            string HeaderText = HeaderTextBlock.Text;

            //Add expander state to dictionary
            if (!ExpanderState.ContainsKey(HeaderText))
            {
                //它不在字典中,所以添加它
                ExpanderState.Add(HeaderText, thisExpander.IsExpanded);
            }
            else
            {
                //Its in the dictionary so update it
                ExpanderState[HeaderText] = thisExpander.IsExpanded;
            }

        }


此代码返回扩展器,但由于某些原因,仅返回前两个(可见的):
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            List<DependencyObject> AllExpanders = FindExpanders(recordListViewDogs as DependencyObject);

            Debug.WriteLine("");
            Debug.WriteLine("All found Expanders...");

            foreach (DependencyObject f in AllExpanders)
            {
                Debug.WriteLine(f.ToString());
            }

        }


        private List<DependencyObject> FindExpanders(DependencyObject parent)
        {
            List<DependencyObject> AllExpanders = new List<DependencyObject>();

            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(parent, i);

                if (child.GetType() == typeof(Expander))
                {
                    AllExpanders.Add(child as DependencyObject);
                }

                AllExpanders.AddRange(FindExpanders(child));
            }

            return AllExpanders;
        }


无论是否显示扩展器,我都找不到找到所有扩展器的方法吗?

干杯。
 

约翰·H

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
1,048
地点
挪威
编程经验
10+
//它不在字典中,所以添加它
That is not necessary, 扩张erState[HeaderText]=value adds it or set the existing key. Not sure why you use the header text as key instead of the CollectionViewGroup name, but it shouldn't matter as long as it is a unique group identifier.

我发布的代码的要旨是IsExpanded已在Collapsed / Expanded上注册,这在可见Expander并与用户进行交互时发生。每个扩展器的已加载事件(如果未注册)将用于还原状态,如果未注册,则使用ControlTemplate中定义的默认值。加载事件发生在第一次显示窗口时,并且每次通过过滤刷新视图时都会发生。我看不到该逻辑中缺少任何内容。

你的"代码返回扩展器"我认为这与这里无关。
 

约翰·H

C#论坛主持人
工作人员
已加入
2011年4月23日
留言内容
1,048
地点
挪威
编程经验
10+
我看不到该逻辑中缺少任何内容。
经过重新思考,当您有多个具有相同名称的多级组时,可以看到问题,例如:
group A
-第一组
-第2组
B组
-第一组
-第2组
这两个"group 1" and "group 2"将是两个共享字典中密钥的扩展器。有人会认为使用Expander对象本身作为键可能是一种解决方案,但是在过滤时会重新创建它们,因此新对象始终会到达。
另一个解决方案是合并父组的名称,您可以为"group A group 1" and "group B group 1"。为了获得我认为需要反思的父母,这是使用组合组名的相同代码:
C#:
private Dictionary<string, bool> expandStates = new Dictionary<string, bool>();

private string GetGroupName(CollectionViewGroup group, string name = null)
{
    var flags = BindingFlags.Instance | BindingFlags.NonPublic;
    var parent = (CollectionViewGroup)group.GetType().GetProperty("Parent", flags).GetValue(group);
    return parent is null ? name : GetGroupName(parent, group.Name.ToString() + name);
}

private void Expander_Loaded(object sender, RoutedEventArgs e)
{
    var expander = (Expander)sender;
    var group = (CollectionViewGroup)expander.DataContext;
    if (expandStates.TryGetValue(GetGroupName(group), out var value))
        expander.IsExpanded = value;
}

private void Expander_ExpandedCollapsed(object sender, RoutedEventArgs e)
{
    var expander = (Expander)sender;
    var group = (CollectionViewGroup)expander.DataContext;
    expandStates[GetGroupName(group)] = expander.IsExpanded;
}
 

456

成员
已加入
2020年4月19日
留言内容
6
编程经验
1-3
@约翰·H - 极好的!那真的很好...我不欣赏您采用的方法,这就是为什么我在第5篇文章中的代码与您的建议不同的原因。

感谢您的帮助!
 
最佳 底部