WPF:不删除视图实现ItemsControl的ObservableCollection筛选
解决方案:通过ViewModel属性控制视图显隐,避免删除集合项
不需要修改ItemsSource集合,而是给每个子项ViewModel添加显隐控制属性,结合ItemTemplate和自定义面板的逻辑实现需求,步骤如下:
- 给ItemViewModel添加显隐属性
在ItemViewModel中新增IsVisible布尔属性并实现INotifyPropertyChanged,用来标记当前项是否需要显示:
public class ItemViewModel : INotifyPropertyChanged { private bool _isVisible = true; public bool IsVisible { get => _isVisible; set { _isVisible = value; OnPropertyChanged(); } } // 原有属性(如ID)和INotifyPropertyChanged实现 public int ID { get; set; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
- 主ViewModel实现双击切换逻辑
在绑定ObsvColl的主ViewModel中,添加选中项管理和切换显隐的命令,双击时切换所有项的IsVisible状态:
public class MainViewModel : INotifyPropertyChanged { public ObservableCollection<ItemViewModel> ObsvColl { get; set; } private ItemViewModel _selectedItem; public ItemViewModel SelectedItem { get => _selectedItem; set { _selectedItem = value; OnPropertyChanged(); } } private bool _isMaximized; public ICommand ToggleMaximizeCommand { get; } public MainViewModel() { ObsvColl = new ObservableCollection<ItemViewModel>(); ToggleMaximizeCommand = new RelayCommand(ToggleMaximize); // 初始化集合逻辑... } private void ToggleMaximize() { if (SelectedItem == null) return; _isMaximized = !_isMaximized; foreach (var item in ObsvColl) { item.IsVisible = _isMaximized ? item == SelectedItem : true; } } // INotifyPropertyChanged实现 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
注:RelayCommand是WPF中常见的命令实现类,你可以用自己的命令基类替代。
- 修改XAML绑定显隐和命令
在ItemsControl的ItemTemplate中,将控件的可见性绑定到IsVisible属性,同时绑定双击命令到主ViewModel的切换命令:
<Window.Resources> <BooleanToVisibilityConverter x:Key="BoolToVis" /> </Window.Resources> <ItemsControl Grid.Row="1" ItemsSource="{Binding ObsvColl}" Background="Black" SelectedItem="{Binding SelectedItem}"> <!-- 原有ItemsPanel设置 --> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Layout:CustomPanel /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate DataType="{x:Type VM:ItemViewModel}"> <Border BorderThickness="3" Visibility="{Binding IsVisible, Converter={StaticResource BoolToVis}}" IsHitTestVisible="{Binding IsVisible}"> <DMC:UserControl Center="{Binding DataContext.Center, Source={x:Reference Name=SummaryView}}" ID="{Binding ID}" ClipToBounds="True"> <DMC:UserControl.InputBindings> <MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.ToggleMaximizeCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" CommandParameter="{Binding}" /> </DMC:UserControl.InputBindings> </DMC:UserControl> </Border> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
IsHitTestVisible绑定是为了让隐藏的项不响应鼠标交互,避免误操作。
- 修正CustomPanel的布局逻辑
如果你的CustomPanel在元素设置为Visibility.Collapsed时仍会预留空间,需要修改面板的测量和排列逻辑,跳过不可见元素:
public class CustomPanel : Panel { protected override Size MeasureOverride(Size availableSize) { Size totalSize = new Size(); foreach (UIElement child in Children) { if (child.Visibility != Visibility.Visible) continue; child.Measure(availableSize); // 你的原有测量逻辑... } return totalSize; } protected override Size ArrangeOverride(Size finalSize) { foreach (UIElement child in Children) { if (child.Visibility != Visibility.Visible) continue; // 你的原有排列逻辑,比如设置元素位置和大小 child.Arrange(new Rect(/* 目标位置和尺寸 */)); } return finalSize; } }
这样处理后,双击操作只会修改子项ViewModel的显隐属性,不会删除集合项,视图不会被销毁,恢复时直接将所有项的IsVisible设为true即可,完全避免了重渲染的问题,同时也不会留下空白布局。
内容的提问来源于stack exchange,提问作者Harxish
相关产品推荐
相关产品推荐

