基于ListView选中状态动态控制控件可见性的实现
实现多选ListView选中项显示/隐藏TimePicker面板
我有一个支持多选的ListView,用于展示多名人员信息。需求如下:
- 选中某人员时,显示其对应的包含TimePicker控件的StackPanel
- 取消选中该人员时,隐藏该StackPanel
当前代码
XAML
<ContentDialog.Resources> <DataTemplate x:Key="ContactListViewTemplate" x:DataType="model:Client"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Ellipse x:Name="Ellipse" Grid.RowSpan="2" Width ="32" Height="32" Margin="6" VerticalAlignment="Center" HorizontalAlignment="Center" Fill="{ThemeResource SystemControlBackgroundBaseMediumBrush}"/> <TextBlock Grid.Column="1" Text="{x:Bind FirstName}" x:Phase="1" Style="{ThemeResource BaseTextBlockStyle}" Margin="12,6,0,0"/> <TextBlock Grid.Column="1" Grid.Row="1" Text="{x:Bind LastName}" x:Phase="2" Style="{ThemeResource BodyTextBlockStyle}" Margin="12,0,0,6"/> <StackPanel Orientation="Horizontal" x:Name="timePanel" Grid.Column="2" Grid.RowSpan="2" Visibility="Collapsed"> <TimePicker Header="Start"></TimePicker> <TimePicker Header="End"></TimePicker> </StackPanel> </Grid> </DataTemplate> </ContentDialog.Resources> <ListView x:Name="clientsList" SelectionMode="Multiple" ItemTemplate="{StaticResource ContactListViewTemplate}" SelectionChanged="ClientsList_SelectionChanged" />
Code Behind
private void ClientsList_SelectionChanged(object sender, SelectionChangedEventArgs e) { Debug.WriteLine("called mett " + e.AddedItems.Count); var listView= sender as ListView; if(e.RemovedItems.Count > 0) { } if(e.AddedItems.Count > 0) { foreach(var item in e.AddedItems) { ListViewItem myListViewItem = (ListViewItem)item; ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myListViewItem); StackPanel stackPanel = myContentPresenter.ContentTemplate.FindDescendant("timePanel") as StackPanel; } } }
解决方案
方法一:修正Code Behind实现
原代码核心错误是将AddedItems中的数据模型(Client对象)直接转换为ListViewItem,这是不正确的。需要通过ListView的ContainerFromItem方法获取对应项的容器,再查找目标面板。
首先补充视觉树查找方法:
private T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is T tChild) return tChild; else { var result = FindVisualChild<T>(child); if (result != null) return result; } } return null; } private DependencyObject FindDescendant(this DataTemplate template, string name) { var frameworkElement = template.LoadContent() as FrameworkElement; return frameworkElement?.FindName(name); }
然后修改SelectionChanged事件处理逻辑:
private void ClientsList_SelectionChanged(object sender, SelectionChangedEventArgs e) { var listView = sender as ListView; if (listView == null) return; // 处理选中项,显示面板 foreach (var client in e.AddedItems.OfType<model:Client>()) { var listViewItem = listView.ContainerFromItem(client) as ListViewItem; if (listViewItem != null) { var contentPresenter = FindVisualChild<ContentPresenter>(listViewItem); if (contentPresenter != null && contentPresenter.ContentTemplate != null) { var timePanel = contentPresenter.ContentTemplate.FindDescendant("timePanel") as StackPanel; timePanel?.SetValue(VisibilityProperty, Visibility.Visible); } } } // 处理取消选中项,隐藏面板 foreach (var client in e.RemovedItems.OfType<model:Client>()) { var listViewItem = listView.ContainerFromItem(client) as ListViewItem; if (listViewItem != null) { var contentPresenter = FindVisualChild<ContentPresenter>(listViewItem); if (contentPresenter != null && contentPresenter.ContentTemplate != null) { var timePanel = contentPresenter.ContentTemplate.FindDescendant("timePanel") as StackPanel; timePanel?.SetValue(VisibilityProperty, Visibility.Collapsed); } } } }
方法二:使用数据绑定(推荐)
这种方式符合MVVM设计模式,无需直接操作视觉元素,维护性更强。
- 给
Client模型添加IsSelected属性(需实现INotifyPropertyChanged接口):
public class Client : INotifyPropertyChanged { public string FirstName { get; set; } public string LastName { get; set; } private bool _isSelected; public bool IsSelected { get => _isSelected; set { if (_isSelected != value) { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); } } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
- 在XAML中添加转换资源并绑定面板可见性:
<ContentDialog.Resources> <BooleanToVisibilityConverter x:Key="BoolToVis"/> <DataTemplate x:Key="ContactListViewTemplate" x:DataType="model:Client"> <!-- 保留原Grid布局内容 --> <StackPanel Orientation="Horizontal" x:Name="timePanel" Grid.Column="2" Grid.RowSpan="2" Visibility="{x:Bind IsSelected, Converter={StaticResource BoolToVis}}"> <TimePicker Header="Start"></TimePicker> <TimePicker Header="End"></TimePicker> </StackPanel> </DataTemplate> </ContentDialog.Resources>
- 同步选中状态:
private void ClientsList_SelectionChanged(object sender, SelectionChangedEventArgs e) { foreach (var client in e.AddedItems.OfType<model:Client>()) { client.IsSelected = true; } foreach (var client in e.RemovedItems.OfType<model:Client>()) { client.IsSelected = false; } }
内容的提问来源于stack exchange,提问作者Nuvolari
相关产品推荐
相关产品推荐

