How can I hide a ListViewItem in a bound ListView? Note: I do not want to remove it.
-
Hide it forever? Or is this after some action? More details would be good...Aaron McIver– Aaron McIver2010-10-29 21:48:20 +00:00Commented Oct 29, 2010 at 21:48
-
Its because i have a listview bbinded to a list. if i want to remove a item from listview i have to removeit on the list. so i didnt want that. thne i think is better t just hide it.user143887– user1438872010-11-03 13:57:49 +00:00Commented Nov 3, 2010 at 13:57
-
1Remember to mark the right answer.Jerry Nixon– Jerry Nixon2011-08-19 15:46:21 +00:00Commented Aug 19, 2011 at 15:46
7 Answers
Yeah, this is easy.
The first thing you need to do is to add a property to the class you are binding to. For example, if you are binding to a User class with FirstName and LastName, just add a Boolean IsSupposedToShow property (you can use any property you like, of course). Like this:
class User: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string FirstName { get; set; }
public string LastName { get; set; }
private bool m_IsSupposedToShow;
public bool IsSupposedToShow
{
get { return m_IsSupposedToShow; }
set
{
if (m_IsSupposedToShow == value)
return;
m_IsSupposedToShow = value;
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs("IsSupposedToShow"));
}
}
}
Then, remember, to hide some item, don't do it in the UI - no no no! Do it in the data. I mean, look for the User record that you want to hide and change that property in it behind the scenes (like in a View Model) - let the UI react. Make the XAML obey the data.
Like this:
<DataTemplate DataType="{x:Type YourType}">
<DataTemplate.Resources>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSupposedToShow}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<!-- your UI here -->
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="LastName" />
<Binding Path="FirstName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
When you change IsSupposedToShow to false, then the XAML understands it is supposed to change the visibility of the whole DataTemplate. It's all wired up for you by WPF and presto, it's what you wanted in your question!
Best of luck!
4 Comments
INotifyPropertyChanged in the User class and raise PropertyChange when IsSupposedToShow changes. (Also, I'd name that property IsVisible.)The approaches that I'd follow, from most to least preferable:
- In
ListView.ItemContainerStyle, use aDataTriggerto setVisibilitybased on a bound property. - Use a style in the
ItemTemplate, or in theDataTemplatefor the items if you're getting default templates from the resource dictionary. - Set the
ItemsSourcefor theListViewto aCollectionView, and handle theCollectionView'sFilterevent in code-behind. See MSDN's discussion of collection views for details. - Maintain a separate
ObservableCollectionas theItemsSourcefor theListViewand add/remove items as appropriate.
Under no circumstances would I use a ValueConverter, because I have a possibly-irrational distaste for them.
I think that using a CollectionView is probably the most correct way of doing this, but they're kind of inelegant because you have to write an event handler to implement filtering.
4 Comments
ItemContainerStyle by default. It's just as simple, and it also means that the behavior is localized to the ListView. This can be an important distinction when the DataTemplate isn't defined locally to the ListView.Use a style with a trigger to set the items visibility to collapsed.
1 Comment
This page gave me the answer I needed: http://www.abhisheksur.com/2010/08/woring-with-icollectionviewsource-in.html (See section "Filtering".)
Wow, so much easier than XAML.
Example:
bool myFilter(object obj)
{
// Param 'obj' comes from your ObservableCollection<T>.
MyClass c = obj as MyClass;
return c.MyFilterTest();
}
// apply it
myListView.Items.Filter = myFilter;
// clear it
myListView.Items.Filter = null;
Comments
The approach with ListView.ItemContainerStyle
<ListView ItemsSource="{Binding Path=Messages}" Grid.Column="1" Grid.Row="1" x:Name="Messages"
SelectedItem="{Binding Path=SelectedMessage, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False" >
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate >
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<TextBlock VerticalAlignment="Center" >
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} => {1}">
<Binding Path="AuthorName" />
<Binding Path="ReceiverName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Jerry Nixon's answer did not work for me completely. I've got to change the xaml a little bit.
Collapsed list view item was using small layout space when I was using DataTemplate.Resources,
Comments
<ItemsControl>
<ItemTemplate>
<DataTemplate>
<Image Visibility='{Binding Converter=my:MaybeHideThisElementConverter}' />
</Image>
</DataTemplate>
</ItemTemplate>
</ItemsControl>
What we're doing here is delegating the decision to your implementation of MaybeHideThisElementConverter. This is where you might return Collapsed if the User property of your object is null, or if the Count is an even number, or whatever custom logic your application requires. The converter will be passed each item in your collection, one by one, and you can return either Visibility.Collapsed or Visibility.Visible on a case by case basis.
Comments
Another approach with converter and ListView.ItemContainerStyle this time with Setter property of Visibility instead of Data Triggers. I prefer this approach if you have complicated business logic about how to decide if show or collapse. Also this approach allows reuse of the converter on multiple screens.
Here is a ListView snippet from XAML. Remember to substitute Rows for your list:
<ListView ItemsSource="{Binding Rows}">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Visibility" Value="{Binding ., Converter={StaticResource RowVisibilityConverter}}" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
Here is the converter snippet. Remember to substitute Row for your class.:
public class RowVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Row row)
{
if (HideRow(row))
{
return Visibility.Collapsed;
}
}
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private static bool HideRow(Row row)
{
// TODO: Complex business logic that returns True or False
}
}
Remember to add the converter declaration to the App.xaml or as a resource of the user control/window or the ListView so everything is wired up.