Search code examples
wpfsilverlightwindows-store-appswindows-store

How to remove/delete an item from a grouped ListBox by clicking a button in its DataTemplate?


I got a tough problem when working on a small Windows Store app using MVVM. I want to display a list of house guests from Big Brother season 14 using ListBox. By design, there should be a button in each ListBoxItem. When the button is clicked, a command should be fired which will remove the ListBoxItem from the ListBox.

Below is my current solution. It works, but not very satisfactory, because whenever an item is removed from the ListBox, the model need to filter the whole collection and refresh the whole ListBox, which cause some performance problem, especially when the original collection is very huge.

[MainPage.xaml]

<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.Resources>
        <CollectionViewSource x:Name="groupInfo" IsSourceGrouped="true" ItemsPath="Items" />
        <local:BigBrotherModel x:Name="BBModel" />
        <DataTemplate x:Key="lvwItemTemp">
            <StackPanel>
                <Grid  Height="30">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="40" />
                        <ColumnDefinition Width="100" />
                        <ColumnDefinition Width="100" />
                    </Grid.ColumnDefinitions>

                    <Button  Grid.Column="0" Padding="0" Foreground="Red" FontFamily="Segoe UI Symbol"
                                Command="{Binding DataContext.RemoveHouseGuestCommand, ElementName=lbxHouseGuests}" 
                                CommandParameter="{Binding}">&#xE221;</Button>
                    <TextBlock Grid.Column="1" Text="{Binding FirstName}" HorizontalAlignment="Stretch" />
                    <TextBlock Grid.Column="2" Text="{Binding LastName}" HorizontalAlignment="Stretch"  />
                </Grid>
            </StackPanel>
        </DataTemplate>
    </Page.Resources>


    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <ListBox x:Name="lbxHouseGuests" DataContext="{StaticResource BBModel}" ItemsSource="{Binding Source={StaticResource groupInfo}}" ItemTemplate="{StaticResource lvwItemTemp}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <ListBox.GroupStyle>
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <Border BorderBrush="Red" BorderThickness="0" Background="DarkGray" HorizontalAlignment="Stretch">
                                <TextBlock Width="500" Text="{Binding Role}"/>
                            </Border>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </GroupStyle>
            </ListBox.GroupStyle>
        </ListBox>
    </Grid>
</Page>

[MainPage.cs]

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Input;
using Windows.Foundation;
using Windows.Foundation.Collections;
using System.Collections.ObjectModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Popups;
using System.Diagnostics;


namespace App1
{


    public class HouseGuest
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Role { get; set; }
        public bool Deleted { get; set; }
    }

    public class BigBrotherModel
    {
        public ObservableCollection<HouseGuest> houseGuests { get; set; }
        public ListBox lbx { get; set; }
        public CollectionViewSource cvs { get; set; }

        public BigBrotherModel()
        {
            houseGuests = new ObservableCollection<HouseGuest>();
            houseGuests.Add(new HouseGuest() { FirstName = "Ian", LastName = "Terry", Role="Player" });
            houseGuests.Add(new HouseGuest() { FirstName = "Shane", LastName = "Meaney", Role = "Player" });
            houseGuests.Add(new HouseGuest() { FirstName = "Wil", LastName = "Heuser", Role = "Player" });
            houseGuests.Add(new HouseGuest() { FirstName = "Danielle", LastName = "Murphree", Role = "Player" });
            houseGuests.Add(new HouseGuest() { FirstName = "Jenn", LastName = "Arroyo", Role = "Player" });
            houseGuests.Add(new HouseGuest() { FirstName = "Jodi", LastName = "Rollins", Role = "Player" });
            houseGuests.Add(new HouseGuest() { FirstName = "Ashley", LastName = "Iocco", Role = "Player" });

            houseGuests.Add(new HouseGuest() { FirstName = "Britney", LastName = "Haynes", Role = "Coach" });
            houseGuests.Add(new HouseGuest() { FirstName = "Dan", LastName = "Gheesling", Role = "Coach"});
            houseGuests.Add(new HouseGuest() { FirstName = "Janelle", LastName = "Pierzina", Role = "Coach" });
            houseGuests.Add(new HouseGuest() { FirstName = "Mike", LastName = "Boogie", Role = "Coach"});


            RemoveHouseGuestCommand = new DelegateCommand(RemoveHouseGuest);
        }
        public ICommand RemoveHouseGuestCommand { get; set; }

        void RemoveHouseGuest(object param)
        {
            Debug.Assert(param is HouseGuest);
            (param as HouseGuest).Deleted = true;
            RefreshListBox();
        }

        object GetGroupedView()
        {
            var view = from hg in houseGuests
                       where hg.Deleted == false
                       group hg by hg.Role into g
                       orderby g.Key
                       select new { Role = g.Key, Items = g };
            return view;
        }

        public void RefreshListBox()
        {
            cvs.Source = GetGroupedView();
        }
    }

    public sealed partial class MainPage : Page
    {

        public MainPage()
        {
            this.InitializeComponent();
            BBModel.cvs = groupInfo;
            BBModel.lbx = lbxHouseGuests;

            BBModel.RefreshListBox();
        }
    }
}

My question is: is there a way to delete a ListBoxItem from a grouped ListBox without the need to refresh the whole ListBox using MVVM? I am really stuck here. Any suggestion would be appreciated. Many thanks in advance.

[Edit] Thanks for Nate's suggestion, I rewrite my code in MainPage.cs, and it works great.Here is the source code.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Input;
using Windows.Foundation;
using Windows.Foundation.Collections;
using System.Collections.ObjectModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Popups;
using System.Diagnostics;
using System.Collections.Specialized;


namespace App1
{


    public class HouseGuest
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Role { get; set; }
        public bool Deleted { get; set; }
    }

    public class HouseGuestGroup : IGrouping<string, HouseGuest>
    {
        public ObservableCollection<HouseGuest> Items { get; set; }
        public string Role { get; set; }
        public HouseGuestGroup()
        {
            Items = new ObservableCollection<HouseGuest>();
        }

        public string Key
        {
            get { return Role; }
        }

        public IEnumerator<HouseGuest> GetEnumerator()
        {
            return Items.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Items.GetEnumerator();
        }


    }

    public class BigBrotherModel : ObservableCollection<HouseGuestGroup>
    {
        public BigBrotherModel()
        {
            RemoveHouseGuestCommand = new DelegateCommand(RemoveHouseGuest);
        }

        public ICommand RemoveHouseGuestCommand { get; set; }

        void RemoveHouseGuest(object param)
        {
            Debug.Assert(param is HouseGuest);
            HouseGuest guest = param as HouseGuest;

            foreach (var g in Items)
            {
                if (g.Role == guest.Role)
                {
                    g.Items.Remove(guest);
                    break;
                }
            }
        }

    }

    public sealed partial class MainPage : Page
    {

        public MainPage()
        {
            this.InitializeComponent();

            HouseGuestGroup guestGroup;

            guestGroup = new HouseGuestGroup();
            guestGroup.Role = "Coach";
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Britney", LastName = "Haynes", Role = "Coach" });
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Dan", LastName = "Gheesling", Role = "Coach" });
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Janelle", LastName = "Pierzina", Role = "Coach" });
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Mike", LastName = "Boogie", Role = "Coach" });
            BBModel.Add(guestGroup);



            guestGroup = new HouseGuestGroup();
            guestGroup.Role = "Player";
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Ian", LastName = "Terry", Role = "Player" });
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Shane", LastName = "Meaney", Role = "Player" });
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Wil", LastName = "Heuser", Role = "Player" });
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Danielle", LastName = "Murphree", Role = "Player" });
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Jenn", LastName = "Arroyo", Role = "Player" });
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Jodi", LastName = "Rollins", Role = "Player" });
            guestGroup.Items.Add(new HouseGuest() { FirstName = "Ashley", LastName = "Iocco", Role = "Player" });
            BBModel.Add(guestGroup);

            groupInfo.Source = BBModel;
        }
    }
}

btw, I think it's better to leave this question open, in hoping that some much better solutions can come out finally.


Solution

  • What is happening is the LINQ statement is creating a new list every time, so when you are affecting the observable collection, it is not being reflected as they are two different lists. What you'll have to do is to create a custom Group object which has an ObservableCollection in side of it to store the guests. Then, you'll have to create an ObservableCollection of these Groups. Then, you can make any changes you want to the static collection (or any subcollections) and it will be reflected in your view.

    Then, you can do something simple and direct to the given observable collection like

    void RemoveHouseGuest(object param)
    {
        Debug.Assert(param is HouseGuest);
        if(houseGuests.Contains(param as HouseGuest))
            houseGuests.Remove(param);
        //(param as HouseGuest).Deleted = true;
        //RefreshListBox();
    }
    

    Hope this helps and happy coding!