wpf - WPF过滤的动态菜单
问题描述
我正在努力让 WPF 中的 UserControl 工作,它有一个填充了 ItemsSource 的 MenuItem,它创建了一个深度为 n 级的菜单(尽管我现在只是在看 TopMenuItem\Branches\Leaves)。我遇到的问题是我想通过嵌入菜单的文本框过滤叶子。如果一个分支没有叶子,它也会被过滤掉。目前看起来像这样:
我正在使用 IMenuTreeItem 的 ObservableCollection,它可以包含分支(它又具有 IMenuTreeItem 的 ObservableCollection)或叶子。
public interface IMenuTreeItem
{
string Name { get; set; }
}
public class MenuTreeLeaf : IMenuTreeItem
{
public string Name { get; set; }
public Guid UID { get; set; }
public ObjectType Type { get; set; }
public Requirement Requirement { get; set; }
public MenuTreeLeaf(string name, ObjectType type, Guid uID)
{
Type = type;
Name = name;
UID = uID;
}
public MenuTreeLeaf(string name)
{
Name = name;
}
}
public class MenuTreeBranch : IMenuTreeItem, INotifyPropertyChanged
{
public string Name { get; set; }
private ObservableCollection<IMenuTreeItem> _items;
public ObservableCollection<IMenuTreeItem> Items
{
get
{
return _items;
}
set
{
_items = value; OnPropertyChanged();
}
}
public MenuTreeBranch(string name)
{
Name = name;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
这就是我过滤的方式。感觉有更好的方法。
ObservableCollection<IMenuTreeItem> result = new ObservableCollection<IMenuTreeItem>(ItemsSource);
for (int i = 0; i < result.Count; i++)
{
if (result[i] is MenuTreeBranch currentBranch)
{
if (currentBranch.Items != null)
currentBranch.Items = new ObservableCollection<IMenuTreeItem>(currentBranch.Items.Where(x => x.Name.ToLower().Contains(SearchField.ToLower())));
}
}
result = new ObservableCollection<IMenuTreeItem>(result.Where(x => (x as MenuTreeBranch).Items.Count > 0));
result.Insert(0, new MenuTreeLeaf("[Search]"));
return result;
所以我的主要问题是:
- 过滤后,我不能再取消过滤。ItemsSource 也发生了变化。可能是因为我在 ItemsSourceFiltered getter 中进行过滤吗?我试图克隆,但是,没有改变任何东西
- 每当文本框中的文本发生更改时,当我在 ItemsSourceFiltered 上调用 OnPropertyChanged 时,菜单就会关闭。输入文本时绝对不应该关闭菜单。
有什么建议吗?
解决方案
您可能有一个菜单项类,它公开一个递归过滤器字符串和一个返回过滤子项的集合属性:
public class FilteredMenuItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; set; }
public ICommand Command { get; set; }
private string filter;
public string Filter
{
get { return filter; }
set
{
filter = value;
foreach (var childItem in ChildItems)
{
childItem.Filter = filter;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Filter)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(FilteredChildItems)));
}
}
public List<FilteredMenuItem> ChildItems { get; set; } = new List<FilteredMenuItem>();
public IEnumerable<FilteredMenuItem> FilteredChildItems
{
get { return string.IsNullOrEmpty(Filter)
? ChildItems
: ChildItems.Where(childItem => (bool)childItem.Name?.Contains(Filter)); }
}
}
RootItem
在视图模型中有一个属性,例如
public FilteredMenuItem RootItem { get; }
= new FilteredMenuItem { Name = "Items" };
您可以像这样在 XAML 中绑定到它:
<StackPanel DataContext="{Binding RootItem}">
<TextBox Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged}"/>
<Menu>
<Menu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="ItemsSource"
Value="{Binding FilteredChildItems}"/>
</Style>
</Menu.Resources>
<MenuItem/>
</Menu>
</StackPanel>
当您填充ChildItems
每个 FilteredMenuItem 的属性时,视图只显示FilteredChildItems
集合。
您可能还注意到,上述内容根本没有使用ObservableCollection
,因为在运行时没有向任何集合添加或删除任何项目。您只需确保在加载视图之前填充项目树。
推荐阅读
- html - 如何通过单击按钮从数组中加载选定的选项 JSON 对象?
- reactjs - 如何引用 React 组件的值
- opencv - 使用 Python 对视频进行密集光流和对象检测
- wordpress - 如何将特定的 htaccess 转换为全局 httpd.conf
- extjs - 构建 ExtWebComponents 图表 Web 组件时出现错误,没有匹配的文件,如何解决?
- javascript - 如何更改现有 D3 可视化的数据并执行转换?
- python - Python IDE 说我的“computer_score + 1”和“user_score + 1”没有效果,知道为什么吗?
- ionic-framework - 如何更改我的 Ionic React babel 配置?
- python - 如何找到表示为字符串和部分分数的两个数字的中点,例如。'1 1/2 - 2'?
- python - 具有最接近值的熊猫数据框自定义插补