当 DataTemplate 子项折叠时如何隐藏 ListView 项目占位符?

How do you hide a ListView Item placeholder when it's DataTemplate child is collapsed?

CarViewControl 的可见性设置为折叠时,它仍会在原来的位置显示一个占位符(请参见下面的屏幕截图)。

有什么方法可以在 ListViewItem 折叠时完全隐藏它?

XAML代码

<ScrollViewer>
    <ListView ItemsSource="{Binding CarVM.UserCars}" ShowsScrollingPlaceholders="False">
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.ItemTemplate>
            <DataTemplate>
                <ctrl:CarViewControl Car="{Binding}" Visibility="{Binding HideCar, Converter={ThemeResource InverseVisConverter}}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ScrollViewer>

在上图中,有 3 个 CarViewControls 已折叠,其后有 1 个未折叠。一个是突出显示的。我希望它们在内容折叠时完全不可见。

我尝试过的:

折叠要求的原因

在每个 CarViewControl 中,存在一个包含安全令牌的 WebView(它维护 WebView 已登录到特定网站)。如果您尝试通过引用传递 WebView,由于我只能假设是安全措施,您将丢失该安全令牌并且必须重新登录该站点。这就是为什么 adding/removing 来自 ObservableCollection 的控件在我的情况下不起作用。

我不会尝试通过 UI 隐藏项目,而是从 ItemsSource 绑定的视图或集合中删除项目。这是一种更简洁的方法,UI 永远不必担心项目的可见性。

编辑 1

When a user selects a specific car from the quick select menu, it shows that car and hides the others.

有道理;让我们调用该视图 "UserCars",并假设 ItemsSource 绑定到 UserCars。

怎么样:将 ItemsSource 更改为绑定到 "SelectedCollection"。当您想显示 UserCars 时,将 SelectedCollection 指向 UserCars 集合。

要限制集合,您只需将 SelectedCollection 指向您仅填充 UserCars.SelectedItem

的新 SingleCar

XAML:

<ListView ItemsSource="{Binding SelectedCollection}" SelectedItem="{Binding SelectedCar}">

ViewModel:

private Car _selectedCar;
public Car SelectedCar { 
  get { return _selectedCar; } 
  set { _selectedCar = value; OnPropertyChanged("SelectedCar"); }
}

private ObservableCollection<Car> _selectedCollection = CarVM.UserCars;
private ObservableCollection<Car> SelectedCollection { 
  get { return _selectedCollection; } 
  set { _selectedCollection = value; OnPropertyChanged("SelectedCollection"); }
}

private ObservableCollection<Car> _originalCollectionForReferenceKeepingOnly;

// called via RelayCommand
public void UserJustSelectedACar()
{
    ObservableCollection<Car> singleItemCollection =  new ObservableCollection<Car>();
    singleItemCollection.Add(SelectedCar);
    _originalCollectionForReferenceKeepingOnly = SelectedCollection;
    SelectedCollection = singleItemCollection;
}

public void ReturnToFullUsedCarsList()
{
    SelectedCollection = _originalCollectionForReferenceKeepingOnly;
}

编辑 2

您似乎试图通过隐藏这些其他项来使 ListView 伪装成 "Car Details" 视图。这本质上是个坏主意;一个不同的 UI 元素绑定到 Listview 的 Selected Car Item 将是一个更好的解决方案。由于新的详细信息面板只会查看已生成的 Car 实例,因此您不会招致任何数据命中。即使你现在可以让这种方法奏效,我担心你将来会给自己带来更多的悲伤。

我会说你的设计有缺陷,但我无法修复;所以我会提供一个 "workaround."

问题是您的 DataTemplate 正在倒塌,这很好,但显然它所在的容器不会倒塌。这不会自然发生,因为 parent 不会继承自 child。第一个认识是每个项目都包含在 ListViewItem 中,您可以通过设置 ItemContainerStyle 观察到这一点。这给您留下了两个解决方案(解决方法)。您可以在 ListViewItem 上设置一些触发器,也可以像我一样做一些更简单的事情——如果您不介意 UI 的影响。

我的完整工作申请如下。要点是您必须编辑 ListViewItem 的 layout/behavior。在我的示例中,默认值不是 BorderThickeness 并且 Padding 不是“0, 0, 0, 0”...将它们设置为 0 将完全隐藏您的项目。

MainWindow.xaml

<Window x:Class="CollapsingListViewItemContainers.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:CollapsingListViewItemContainers"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BoolToVisConverter"></BooleanToVisibilityConverter>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <Button Content="Disappear Car 3" Click="Button_Click" />
            <ListView ItemsSource="{Binding Cars}">
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                        <Setter Property="MinHeight" Value="0" />
                        <Setter Property="Padding" Value="0 0 0 0" />
                    </Style>
                </ListView.ItemContainerStyle>
                <ListView.ItemTemplate>
                    <DataTemplate DataType="{x:Type local:Car}">
                        <Grid Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisConverter}}">
                            <StackPanel>
                                <TextBlock Text="{Binding Name}" />
                                <TextBlock Text="{Binding Title}" />
                                <TextBlock Text="{Binding Id}" />
                            </StackPanel>
                        </Grid>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackPanel>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace CollapsingListViewItemContainers
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public ObservableCollection<Car> _cars = new ObservableCollection<Car>
                        {
                            new Car("Not yours", "Mine", 1),
                            new Car("Not mine", "Yours", 2),
                            new Car("Not ours", "His", 3),
                            new Car("Not ours", "Hers", 4),
                        };
        public ObservableCollection<Car> Cars
        {
            get
            {
                return _cars;
            }
        }
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Cars[2].IsVisible = !Cars[2].IsVisible;
        }
    }

    public class Car : INotifyPropertyChanged
    {
        private bool _isVisible;
        public bool IsVisible
        {
            get
            {
                return _isVisible;
            }
            set
            {
                _isVisible = value;
                NotifyPropertyChanged("IsVisible");
            }
        }
        public string Name
        {
            get; set;
        }

        public string Title
        {
            get; set;
        }

        public int Id
        {
            get; set;
        }

        public Car(string name, string title, int id)
        {
            Name = name;
            Title = title;
            Id = id;
            IsVisible = true;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string propertyName)
        {
            if(PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

编辑

老实说,以上是一个非常便宜的解决方案,我又考虑了 3 分钟后对它不满意。我不满意的原因是,如果您可以使用键盘,您仍然可以 select 该项目。在上面的示例中,单击第一项,"hide" 项,然后使用鼠标,ListView.SelectedItem 仍然会发生变化。

下面是一个快速解决方案(解决方法 :D),可以实际从列表中删除项目并防止它们获得焦点。用这个替换 ListView.ItemContainerStyle 并更改 ActualHeight 触发器值以匹配您看到的值。这将根据我相信的 OS 个主题而改变——我会把它留给你来测试。最后,请记住 ListViewItem.DataContext 将成为 ItemsSource 中的一项。这意味着绑定到 IsVisibleDataTrigger 绑定到 Car.IsVisible 属性。

<ListView.ItemContainerStyle>
    <Style TargetType="ListViewItem">
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        <Style.Triggers>
            <Trigger Property="ActualHeight" Value="4">
                <Setter Property="Visibility" Value="Collapsed" />
            </Trigger>
            <DataTrigger Binding="{Binding IsVisible}" Value="True">
                <Setter Property="Visibility" Value="Visible" />
            </DataTrigger>
        </Style.Triggers>
    </Style>
</ListView.ItemContainerStyle>

编辑 2(在我发布第一个编辑之前编辑。

去他的,不要绑定你的可见性CarViewControl;你不需要。您只需要专注于删除项目本身,一旦项目被删除,包含的控件也将被删除(尽管您应该自己测试并更改 IsTabStopIsFocusable 如果您仍然可以CarViewControl 中的项目)。此外,由于使用带有 ActualHeight 绑定的任意数字不是很安全,只需直接绑定到 IsVisible 属性 (或 HideCar 在您的情况下)并触发可见性ListViewItem 应该足够了。

最后,这是我的决赛XAML:

<Window x:Class="CollapsingListViewItemContainers.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:CollapsingListViewItemContainers"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BoolToVisConverter"></BooleanToVisibilityConverter>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <Button Content="Disappear Car 3" Click="Button_Click" />
            <ListView ItemsSource="{Binding Cars}">
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding IsVisible}" Value="False">
                                <Setter Property="Visibility" Value="Collapsed" />
                            </DataTrigger>
                            <DataTrigger Binding="{Binding IsVisible}" Value="True">
                                <Setter Property="Visibility" Value="Visible" />
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </ListView.ItemContainerStyle>
                <ListView.ItemTemplate>
                    <DataTemplate DataType="{x:Type local:Car}">
                        <Grid>
                            <StackPanel>
                                <TextBlock Text="{Binding Name}" />
                                <TextBlock Text="{Binding Title}" />
                                <TextBlock Text="{Binding Id}" />
                            </StackPanel>
                        </Grid>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackPanel>
    </Grid>
</Window>