How to hide a ListView item when its child DataTemplate has collapsed?
When the parameter's visibility is CarViewControl
set to collapse, it still shows the placeholder where it was before (see screenshot below).
Is there a way to completely hide ListViewItem
when it is tailored?
XAML code
<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>
In the image above, there are three CarViewControls
that are reset and then one that is not. One is highlighted. I want them to be completely invisible when the content collapsed.
What I have tried:
- Setting the height of the control
DataTemplate
to 0 (just to see if it hides a placeholder that had no effect - Customization
ShowsScrollingPlaceholders
basedFalse
on this documentation: MSDN ListView Placeholders
Reason for claiming collapse
Within each CarViewControl, there is a WebView that includes a security token (which maintains that the WebView is registered with a specific website). If you try to pass a WebView by reference, due to what I can only assume, these are security measures, you lose this security token and must re-enter the site. So adding / removing a control from ObservableCollection
won't work in my case.
source to share
I would say your design is flawed, but I cannot fix it; so I'll provide a "workaround".
The problem is yours is DataTemplate
collapsing, which is great, but obviously the container it is in is not collapsing. This will not happen because the parent will not inherit from the child. The first implementation is each element is wrapped in ListViewItem
, and you can see that from setting ItemContainerStyle
. This leaves you with two solutions (workarounds). You can set up some triggers on yours ListViewItem
, or you can do something lighter like me and if you don't mind the influence of the UI.
Below is my complete working application. The main thing is that you need to edit the layout / behavior ListViewItem
. In my example, the default values โโare not BorderThickeness
and are Padding
not "0, 0, 0, 0" ... Setting these parameters to 0 will completely hide your objects.
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));
}
}
}
}
Edit
Let's be honest, the above was a pretty cheap solution and I was not happy with it after thinking about it for another 3 minutes. The reason I'm not happy is because you can select an item if you have keyboard access. In the example above, click the first item, "hide" the items, then use the mouse and ListView.SelectedItem
it will still change.
So this is a quick solution (workaround: D) to actually remove the item from the list and prevent them from getting focus. Replace ListView.ItemContainerStyle
with this and change the trigger value ActualHeight
according to the values โโyou see. This will change depending on which OSes I believe I'll leave for you to check. Finally, remember what ListViewItem.DataContext
will belong to the item in ItemsSource
. This means that DataTrigger
associated with IsVisible
is bound to a property 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>
Edit 2 (edited before I even posted the first edit.
Screw it in, don't bind your visibility CarViewControl
; You do not need. You only need to focus on deleting the element itself, and once the element is deleted, the controls are also deleted (although you should check this yourself and change IsTabStop
and IsFocusable
if you can still insert elements in CarViewControl
). Also, since using an arbitrary number with binding is ActualHeight
not very safe, binding directly to the property IsVisible
(or HideCar
in your case) and triggering the visibility should be sufficient ListViewItem
.
Finally, here is my final 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>
source to share
Instead of trying to hide the item through the UI, I remove the item from the view or collection that the ItemsSource is associated with. This is a cleaner approach and the UI should never bother with the visibility of the element.
EDIT 1
When the user selects a specific car from the quick select menu, it shows that the car is and hides others.
Has the meaning; let this view "UserCars" and suppose ItemsSource is bound to UserCars.
How about this: change the ItemSource to bind to "SelectedCollection". When you want to show UserCars, select SelectedCollection to the UserCars collection.
To constrain the set, you simply point the SelectedCollection to a new SingleCar, which you fill with only UserCars.SelectedItem
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;
}
EDIT 2
It looks like you are trying to make the ListView visible as "Car Parts" by hiding these other items. This is inherently a bad idea; another UI element related to the Listview Selected Car
will provide a better solution. Since the new dashboard will simply look at the already generated instance Car
, you would not be affected by any data. Even though you can make this approach work right now, I'm worried that you're going to just cause yourself more grief in the future.
source to share