как реализует INotifyPropertyChanged, чтобы сделать Observablecollection уведомить, что элементы изменились - PullRequest
1 голос
/ 21 февраля 2020

У меня есть модель представления, которая представляет элементы в ListBox. Существует две коллекции: Source содержит все элементы и Checked содержит только отмеченные элементы. Есть две кнопки SelectAll и ClearAll. Когда я нажимаю sh на одну из этих кнопок, View Model работает хорошо и обновляются коллекции Source и Checked, но никаких изменений в Listbox не происходит.

CheckItemPresenterVM<T> - элемент, который сохраняет состояние одной кнопки и реализует INotifyPropertyChange, но при изменении свойства IsChecked событие CollectionChanged не возникает.

Вопрос в том, как заставить это работать?

    <UserControl.Resources>
    <ItemsPanelTemplate x:Key="listPanelTemplate">
        <WrapPanel Orientation="Horizontal" />
    </ItemsPanelTemplate>


    <Style TargetType="ListBox">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <ScrollViewer HorizontalScrollBarVisibility="Disabled"
                                  VerticalScrollBarVisibility="Hidden">
                        <ItemsPresenter/>
                    </ScrollViewer>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="ListBoxItemIsCheckedBinding"
           TargetType="{x:Type ListBoxItem}">
        <Setter Property="IsSelected"
                Value="{Binding IsChecked, Mode=TwoWay}" />
        <Setter Property="Padding"
                Value="5,3" />
        <Setter Property="Background"
                Value="LightCyan"></Setter>
    </Style>
    <Style TargetType="Button">
        <Setter Property="Padding"
                Value="7, 3" />
        <Setter Property="Margin"
                Value="1" />
    </Style>
    <Style TargetType="ToggleButton">
        <Setter Property="Padding"
                Value="7, 3" />
        <Setter Property="Margin"
                Value="1" />
    </Style>

</UserControl.Resources>
<Grid>
    <Expander Header="Categories"
              IsExpanded="True">

        <Grid>

            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <WrapPanel Grid.Row="0"
                       Margin="0, 5">
                <Button Command="{Binding SelectAll}">Select All</Button>
                <Button Command="{Binding ClearAll}">Clear All</Button>
                <Button Command="{Binding InvertSelection}">Invert Selection</Button>
            </WrapPanel>
            <ListBox Grid.Row="1" />
            <ListBox Name="CategoriesListBox"
                     Grid.Row="2"
                     SelectionMode="Multiple"
                     ItemsSource="{Binding CategoryVM.Source, Mode=TwoWay}"
                     ItemContainerStyle="{DynamicResource ListBoxItemIsCheckedBinding}"
                     ItemsPanel="{StaticResource listPanelTemplate}" />
            <ListBox Name="CheckedListBox"
                     Grid.Row="3"
                     SelectionMode="Multiple"
                     ItemsSource="{Binding CategoryVM.Checked}"/>
        </Grid>
    </Expander>
</Grid>

class TestViewModel
{
    public CheckItemVM<string> CategoryVM { get; set; }

    public TestViewModel()
    {
        logger.FileName = "TestSelectionFilter.txt";
        logger.AddEventRecord(this);

        CategoryVM = new CheckItemVM<string>(
            new List<string>() {
                "01 elem",
                "02 elem",
                "03 elem",
                "04 elem",
                "05 elem",
                "06 elem",
                "07 elem",
                "08 elem",
                "09 elem",
                "10 elem",
                "11 elem",
                "12 elem",
                "13 elem",
                "14 elem",
                "15 elem",
                "16 elem",
                "17 elem",
                "18 elem",
                "19 elem",
                "20 elem",
                "21 elem",
                "22 elem",
                "23 elem",
                "24 elem",
                "25 elem"});
    }

    public ICommand SelectAll
    {
        get
        {
            return new RelayCommand
            {
                ExecuteAction = a =>
                {
                    CategoryVM.SelectAll();
                },
                CanExecutePredicate = p =>
                {
                    return !CategoryVM.IsAllSelected();
                }
            };
        }
    }

    public ICommand InvertSelection
    {
        get
        {
            return new RelayCommand
            {
                ExecuteAction = a =>
                {
                    CategoryVM.InvertSelection();
                },
                CanExecutePredicate = p =>
                {
                    return true;
                }
            };
        }
    }

    public ICommand ClearAll
    {
        get
        {
            return new RelayCommand
            {
                ExecuteAction = a =>
                {
                    CategoryVM.ClearAll();
                },
                CanExecutePredicate = p =>
                {
                    return CategoryVM.Checked.Any();
                }
            };
        }
    }

class CheckItemVM<T>
{

    protected ObservableCollection<CheckItemPresenterVM<T>> _checked = new ObservableCollection<CheckItemPresenterVM<T>>();

    public ObservableCollection<CheckItemPresenterVM<T>> Source { get; set; }
    public ObservableCollection<CheckItemPresenterVM<T>> Checked { get => _checked; }

    public CheckItemVM(ICollection<T> _source)
    {
        Source = new ObservableCollection<CheckItemPresenterVM<T>>();
        UpdateSource(_source);
    }


    protected void UpdateSource(ICollection<T> _source)
    {
        foreach (var item in _source)
        {
            Source.Add(new CheckItemPresenterVM<T>(ref _checked)
            { Item = item, IsChecked = false });
        }
    }

    public void SetSelection(CheckItemPresenterVM<T> sourceItem, bool flag)
    {
        if (sourceItem.IsChecked != flag)
        {
            sourceItem.IsChecked = flag;
        }
    }

    public void SelectAll()
    {
        foreach (var item in Source)
        {
            item.IsChecked = true;
        }
    }

    public void ClearAll()
    {
        foreach (var item in Source)
        {
            item.IsChecked = false;
        }
    }

    public void InvertSelection()
    {
        foreach (var item in Source)
        {
            if (item.IsChecked) item.IsChecked = false;
            else item.IsChecked = true;
        }
    }

    public bool IsAllSelected()
    {
        return Source.Count == Checked.Count;
    }
}


class CheckItemPresenterVM<T> : INotifyPropertyChanged
{
    protected bool _isChecked;
    protected ObservableCollection<CheckItemPresenterVM<T>> _checked;

    public CheckItemPresenterVM(ref ObservableCollection<CheckItemPresenterVM<T>> Checked)
    {
        _checked = Checked;
    }

    public T Item { get; set; }
    public string Name { get; set; }

    public bool IsChecked
    {
        get
        {
            return _isChecked;
        }
        set
        {
            _isChecked = value;
            if (value)
            {
                if (!_checked.Contains(this))
                {
                    _checked.Add(this);
                    NotifyPropertyChanged("Item was Checked");
                }
            }
            else
            {
                if (_checked.Contains(this))
                {
                    _checked.Remove(this);
                    NotifyPropertyChanged("Item Unchecked");
                }
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public override string ToString()
    {
        return Item.ToString();
    }
}

Ответы [ 2 ]

0 голосов
/ 21 февраля 2020

Я вижу странную часть вашего кода. Угадайте это исправление в PropertyChanged повышающем вызове

public bool IsChecked
    {
        get
        {
            return _isChecked;
        }
        set
        {
            _isChecked = value;
            if (value)
            {
                if (!_checked.Contains(this))
                {
                    _checked.Add(this);
                    NotifyPropertyChanged("IsChecked");
                }
            }
            else
            {
                if (_checked.Contains(this))
                {
                    _checked.Remove(this);
                    NotifyPropertyChanged("IsChecked");
                }
            }
        }
    }

NotifyPropertyChanged fires PoropertyChanged Событие со строковым параметром, который вы передаете. Тем временем Binding получит его.

Итак, вот 2 друга

<Setter Property="IsSelected" Value="{Binding IsChecked, Mode=TwoWay}" />

и

NotifyPropertyChanged("IsChecked");

Если вам нужно настроить обновление Поведение вашего элемента управления, передайте UpdateSourceTrigger значение привязки, вот так:

<Setter Property="IsSelected" Value="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

Дополнительно: TwoWay по умолчанию для Mode здесь. Вы не можете объявить это.

0 голосов
/ 21 февраля 2020

При связывании View / Control с ObservableCollection View / Control будет перерисовываться при возникновении события CollectionChanged ObservableCollection.

Это происходит, когда элемент добавляется, удаляется, переупорядочивается или назначается новый экземпляр; но не (как вы, вероятно, поняли), когда элемент вызывает его событие PropertyChanged.

Чтобы ваши изменения были отражены в пользовательском интерфейсе, вам нужно, чтобы PropertyChanged каждого элемента вызывал событие CollectionChanged коллекции.

public class ObservableItemCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
    // Call this from the constructor
    private void InitialiseItems()
    {
        CollectionChanged += ContentCollectionChanged;

        foreach (T item in Items)
            item.PropertyChanged += ReplaceElementWithItself;
    }

    private void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.OldItems != null)
        {
            foreach (T item in e.OldItems)
            {
                item.PropertyChanged -= ReplaceElementWithItself;
            }
        }

        if (e.NewItems != null)
        {
            foreach (T item in e.NewItems)
            {
                item.PropertyChanged += ReplaceElementWithItself;
            }
        }
    }

    private void ReplaceElementWithItself(object sender, PropertyChangedEventArgs e)
    {
        var collectionChangedArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, sender, sender, IndexOf((T)sender));

        // Call this on the main thread
        OnCollectionChanged(collectionChangedArgs);
    }
}

Эта реализация просто вызывает событие CollectionChanged как «Заменить», если один и тот же элемент удален и вставлен с одним и тем же индексом. Помните, что это может привести к непредвиденным последствиям, если у вас есть что-то, что действительно заботится о событиях CollectionChanged, помимо привязки его к пользовательскому интерфейсу; но я нашел самый простой способ добиться того, чего вы хотите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...