Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issues with {RelativeSource PreviousData} when removing collection elements

Tags:

mvvm

binding

wpf

I'm using the following (simplified) code to display an element in all the items in an ItemsControl except the first:

<TheElement Visibility="{Binding RelativeSource={RelativeSource PreviousData},
                                 Converter={StaticResource NullToVisibility}}/>

NullToVisibility is a simple converter that returns Visibility.Hidden if the source is null, Visibility.Visible otherwise.

Now, this works fine when binding the view initially, or adding elements to the list (an ObservableCollection), but the element is not made invisible on the second element when removing the first.

Any ideas on how to fix this?

like image 368
Diego Mijelshon Avatar asked Jul 28 '11 19:07

Diego Mijelshon


2 Answers

Had some wasted code leftover from a previous answer... might as well use it here:

The key is to refresh the viewsource e.g. :

CollectionViewSource.GetDefaultView(this.Categories).Refresh();

enter image description here

Full example source below. Remove First Item removes first element and refreshes the view:

RelativeSourceTest.xaml

<UserControl x:Class="WpfApplication1.RelativeSourceTest"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:PersonTests="clr-namespace:WpfApplication1" mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <PersonTests:NullToVisibilityConvertor x:Key="NullToVisibility"/>
    </UserControl.Resources>
    <Grid>
        <StackPanel Background="White">
            <Button Content="Remove First Item" Click="Button_Click"/>
            <ListBox ItemsSource="{Binding Categories}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <CheckBox IsChecked="{Binding Checked, Mode=TwoWay}" >
                            <StackPanel>
                                <TextBlock Text="{Binding CategoryName}"/>
                                <TextBlock Text="Not The First" 
                                    Visibility="{Binding RelativeSource={RelativeSource PreviousData},
                                        Converter={StaticResource NullToVisibility}}"/>
                            </StackPanel>
                        </CheckBox>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </StackPanel>
    </Grid>
</UserControl>

RelativeSourceTest.xaml.cs

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApplication1
{
    public partial class RelativeSourceTest : UserControl
    {
        public ObservableCollection<Category> Categories { get; set; }

        public RelativeSourceTest()
        {
            InitializeComponent();
            this.Categories = new ObservableCollection<Category>()
                                {
                                    new Category() {CategoryName = "Category 1"},
                                    new Category() {CategoryName = "Category 2"},
                                    new Category() {CategoryName = "Category 3"},
                                    new Category() {CategoryName = "Category 4"}
                                };
            this.DataContext = this;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.Categories.RemoveAt(0);
            CollectionViewSource.GetDefaultView(this.Categories).Refresh();
        }
    }
}

Category.cs

using System.ComponentModel;

namespace WpfApplication1
{
    public class Category : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private bool _checked;
        public bool Checked
        {
            get { return _checked; }
            set
            {
                if (_checked != value)
                {
                    _checked = value;
                    SendPropertyChanged("Checked");
                }
            }
        }

        private string _categoryName;
        public string CategoryName
        {
            get { return _categoryName; }
            set
            {
                if (_categoryName != value)
                {
                    _categoryName = value;
                    SendPropertyChanged("CategoryName");
                }
            }
        }

        public virtual void SendPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}
like image 73
Gone Coding Avatar answered Oct 19 '22 02:10

Gone Coding


It's '17 now, but the problem is here. MVVM approach (as I see it):

public class PreviousDataRefreshBehavior : Behavior<ItemsControl> {
    protected override void OnAttached() {
        var col = (INotifyCollectionChanged)AssociatedObject.Items;
        col.CollectionChanged += OnCollectionChanged;
    }

    protected override void OnDetaching() {
        var col = (INotifyCollectionChanged)AssociatedObject.Items;
        col.CollectionChanged -= OnCollectionChanged;
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
        if(e.Action != NotifyCollectionChangedAction.Remove) {
            return;
        }

        CollectionViewSource.GetDefaultView(AssociatedObject.ItemsSource).Refresh();
    }
}

and usage:

<ItemsControl>
  <int:Interaction.Behaviors>
    <behaviors:PreviousDataRefreshBehavior/>
  </int:Interaction.Behaviors>
</ItemsControl>
like image 25
vlaku Avatar answered Oct 19 '22 04:10

vlaku