locked
How to hide/remove an item from ListView by binding visibility(without filtering the item collection/ItemsSource)? RRS feed

  • Question

  • I want to dynamically hide/remove some items by binding the Visibility of ItemTemplate root panel  to some boolean value on its DataContext object. Yes, I can find the ListViewItem in some event handler by traverse the visual tree generated by the ItemTemplate, but I don't want to follow this way, since first, it's not elegant, second. the ItemTemplate can be in a separated xaml file(resource dictionary), so it's hard to to access the event handler of the ItemTemplate element(Behaviors can do the job, but it's way too complicated). I don't want to filter the item collection each time when an item is to be removed either, for it will introduce great performance penalty.

    So I just tried and stick to the data binding solution. It kind of worked, when the visibility of the root panel is collapsed, it and all its child elements are gone, which is just as expected. But the problem is, a blank space was left there somehow(please see my picture below). So my questions is: what's wrong with my code? How to fix it? Any idea would be appreciated!

    [mainpage.xaml]

    <Page x:Class="App8.MainPage"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:local="using:App8"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          mc:Ignorable="d">
    
        <Page.Resources>
            <local:BooleanToVisibilityConverter x:Key="b2v" />
        </Page.Resources>
    
    
        <Grid Background="Beige">
            <ListView x:Name="lbxHouseGuests"
                      Width="600"
                      Height="500"
                      Margin="50">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal"
                                    Background="Navy"
                                    Visibility="{Binding IsVisible, Converter={StaticResource b2v}}">
                            <TextBlock Text="{Binding Name}" />
                            <Button Command="{Binding HideItemCommand}"
                                    CommandParameter="{Binding}"
                                    Content="Remove me"
                                    Background="Red"/>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
    </Page>

    [mainpage.xaml.cs]

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Windows.Input;
    using Windows.UI.Xaml.Controls;
    
    
    namespace App8
    {
        public class HouseGuest: BindableBase
        {
            public string Name { get; set; }
            public string Role { get; set; }
    
            private Boolean isVisible;
            public Boolean IsVisible
            {
                get { return isVisible; }
                set { SetProperty(ref isVisible, value); }
            }
    
            public ICommand HideItemCommand { get; set; }
            public void HideItem(object param)
            {
                HouseGuest hg = (HouseGuest)param;
                hg.IsVisible = false;
            }
    
            public HouseGuest()
            {
                isVisible = true;
                HideItemCommand = new DelegateCommand(HideItem);
            }
        }
    
    
        public sealed partial class MainPage : Page
        {
    
            public MainPage()
            {
                this.InitializeComponent();
    
                ObservableCollection<HouseGuest> ls = new ObservableCollection<HouseGuest>();
                ls.Add(new HouseGuest() { Name = "Britney", Role = "Coach" });
                ls.Add(new HouseGuest() { Name = "Britney", Role = "Coach" });
                ls.Add(new HouseGuest() { Name = "Britney",  Role = "Coach" });
                ls.Add(new HouseGuest() { Name = "Ian", Role = "Player" });
                ls.Add(new HouseGuest() { Name = "Ian",  Role = "Player" });
                ls.Add(new HouseGuest() { Name = "Ian",  Role = "Player" });
                ls.Add(new HouseGuest() { Name = "Ian",  Role = "Player" });
    
                this.lbxHouseGuests.ItemsSource = ls;
            }
    
        }
    }

    And here is the result when I hide the second item:




    Monday, November 4, 2013 2:47 PM

Answers

  • You don't have to edit the template for this. You can set the bindings in PrepareContainerForItemOverride .

    Setting bindings with this function is demonstrated in the XAML accessibility sample.

    --Rob

    • Marked as answer by silverbird.lee Tuesday, November 5, 2013 6:27 AM
    Tuesday, November 5, 2013 4:36 AM
    Moderator
  • Fixed the problem. The solution is not perfect, but it simply works. Here are the steps:

    1.Get a copy of ListView ItemContainerStyle.(You can do this by right click the ListView control on the design surface, then select "Edit additional Templates"->"Edit generated item container(ItemContainerStyle)")

    2.Bind the Visibility property of the ListViewItemPresenter to your view model, for example:

    Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"

    (you need to change the binding path and converter with respect to your code)

    3.Apply the above style to your ListView. It should work fine. Enjoy!

    • Marked as answer by silverbird.lee Tuesday, November 5, 2013 3:48 AM
    Tuesday, November 5, 2013 3:34 AM

All replies

  • It's still got a space because hiding the element doesn't mean that the elemen doesn't exist in the ListView, and the ListView creates an identical space for every element regardless of what it contains. You'll have to remove the item from the data behind the ListView in order to get rid of the empty space.

    Matt Small - Microsoft Escalation Engineer - Forum Moderator
    If my reply answers your question, please mark this post as answered.

    NOTE: If I ask for code, please provide something that I can drop directly into a project and run (including XAML), or an actual application project. I'm trying to help a lot of people, so I don't have time to figure out weird snippets with undefined objects and unknown namespaces.

    Monday, November 4, 2013 8:19 PM
    Moderator
  • Matt, thank you for the quick reply. So does it mean that to remove a listviewItem completely, I have to filter the ItemsSource? Is there any workaround? Can I achieve my goal just by customizing ControlTemplate or ContainerStyle (or something else) in XAML?

    However,I don't think it is a good idea to remove or delete a listviewitem by moving the object out of the original collection. It is better just to "hide" it, so when user toggle its visibility back to Visibility.Visible, the hidden listviewitem can just reappear in its original place in the right order.

    On the other side, if we remove it completely by removing the behind object from ItemsSource, then we would have to figure out where to insert a removed listviewitem if we want to bring back it again. If the items are grouped and hold by CollectionViewSource, then we will also have to figure out which group the reappeared item is belong to. This would involve some kind of ordering or grouping.

    Sometimes, when the collection is huge, the performance penalty of resorting and regrouping would be intolerable. So maybe it would be better to toggle the visibility just by data binding without filtering the original item collection.






    Tuesday, November 5, 2013 12:36 AM
  • Fixed the problem. The solution is not perfect, but it simply works. Here are the steps:

    1.Get a copy of ListView ItemContainerStyle.(You can do this by right click the ListView control on the design surface, then select "Edit additional Templates"->"Edit generated item container(ItemContainerStyle)")

    2.Bind the Visibility property of the ListViewItemPresenter to your view model, for example:

    Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"

    (you need to change the binding path and converter with respect to your code)

    3.Apply the above style to your ListView. It should work fine. Enjoy!

    • Marked as answer by silverbird.lee Tuesday, November 5, 2013 3:48 AM
    Tuesday, November 5, 2013 3:34 AM
  • You don't have to edit the template for this. You can set the bindings in PrepareContainerForItemOverride .

    Setting bindings with this function is demonstrated in the XAML accessibility sample.

    --Rob

    • Marked as answer by silverbird.lee Tuesday, November 5, 2013 6:27 AM
    Tuesday, November 5, 2013 4:36 AM
    Moderator
  • Thanks, Rob! I followed your suggestion and it worked like a charm.

        public class MyListView : ListView
        {
            protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
            {
                base.PrepareContainerForItemOverride(element, item);
                FrameworkElement source = element as FrameworkElement;
    
                var b2v = (BooleanToVisibilityConverter)Application.Current.Resources["b2v"];
    
                source.SetBinding(ListViewItemPresenter.VisibilityProperty,
                    new Binding { Path = new PropertyPath("Visible"), Converter=b2v});
    
            }
        }

    NOTE: This solution is much more clean and elegant comparing to copying and changing the ItemContainerStyle. 

     
    Tuesday, November 5, 2013 6:34 AM