Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / WPF, Silverlight Новый топик    Ответить
 Когнитивный диссонанс  [new]
Roman Mejtes
Member

Откуда: г. Пермь
Сообщений: 4036
Появилась у меня необходимость сделать ObservableStack<T> класс, в принципе задача рядовая, делается такой класс за пару минут или просто копируется из интернетов. Но проблема не о нём, а о том, с чем я столкнулся в процессе реализации интерфейса INotifyCollectionChanged.
Вот такой код я взял для примера:
+
<Window x:Class="WpfApplication33.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpfApplication33="clr-namespace:WpfApplication33"
        Title="MainWindow" Height="350" Width="525"
        x:Name="Window">
    <Window.Resources>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Width" Value="100"/>
            <Setter Property="Height" Value="25"/>
            <Setter Property="Margin" Value="3"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
            <RowDefinition Height="24"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0">
            <Button Click="PushButton_OnClick" Content="Push" />
            <Button Click="FakePushButton_OnClick" Content="Fake push" />
            <Button Click="PopButton_OnClick" Content="Pop" />
            <Button Click="ClearButton_OnClick" Content="Clear" />
            <Button Click="ViewButton_OnClick" Content="View Stack" />

        </StackPanel>
        <UniformGrid Columns="2" Grid.Row="1" >
            <ItemsControl ItemsSource="{Binding  Persons, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
                          ScrollViewer.VerticalScrollBarVisibility="Visible"
                          ScrollViewer.CanContentScroll="True"
                          Margin="5">
                <ItemsControl.ItemTemplate>
                    <DataTemplate DataType="{x:Type wpfApplication33:Person}">
                        <UniformGrid Columns="3">
                            <TextBlock Text="{Binding Name}"/>
                            <TextBlock Text="{Binding Age}"/>
                            <Border x:Name="FlashBorder" Background="Transparent">

                            </Border>
                        </UniformGrid>
                        <DataTemplate.Triggers>
                            <EventTrigger RoutedEvent="Control.Loaded">
                                <BeginStoryboard>
                                    <Storyboard TargetName="FlashBorder">
                                        <ColorAnimation Storyboard.TargetProperty="Background.(SolidColorBrush.Color)" From="Green" To="Blue" Duration="0:0:1" />
                                    </Storyboard>
                                </BeginStoryboard>

                            </EventTrigger>
                        </DataTemplate.Triggers>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.Template>
                    <ControlTemplate TargetType="{x:Type ItemsControl}">
                        <ScrollViewer>
                            <ItemsPresenter/>
                        </ScrollViewer>
                    </ControlTemplate>
                </ItemsControl.Template>
            </ItemsControl>
            <TextBlock x:Name="StackView" Margin="5"/>
        </UniformGrid>
    </Grid>
</Window>


using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Data;

namespace WpfApplication33
{

    public class ObservableStack<T> : INotifyCollectionChanged, IEnumerable<T>
    {

        private readonly Stack<T> _stack = new Stack<T>();


        public void FakePush(T item)
        {

            //Реального добавления элемента не осуществляется
            var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item);
            OnCollectionChanged(arg);
        }

        public void Push(T item)
        {
            _stack.Push(item);
            var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item);
            OnCollectionChanged(arg);
        }

        public T Pop()
        {
            var pop = _stack.Pop();
            var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, pop, _stack.Count);
            OnCollectionChanged(arg);
            return pop;
        }

        private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null) CollectionChanged(this, e);
        }

        public T Peek()
        {
            var peek = _stack.Peek();
            return peek;
        }

        public void Clear()
        {
            _stack.Clear();
            var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            OnCollectionChanged(arg);
        }

        
        public event NotifyCollectionChangedEventHandler CollectionChanged;


        public IEnumerator<T> GetEnumerator()
        {
            return _stack.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

    }

    public class Person
    {
        public string Name { set; get; }
        public int Age { set; get; }
        
        public override string ToString()
        {
            return string.Format("{0}, {1}", Name, Age);
        }

    }

    public partial class MainWindow : Window
    {

        public ObservableStack<Person> Persons { set; get; }
        private ICollectionView _personsView;

        public MainWindow()
        {
            InitializeComponent();
            Persons = new ObservableStack<Person>();
            _personsView = CollectionViewSource.GetDefaultView(Persons);
        }

        private int _count;

        private void PushButton_OnClick(object sender, RoutedEventArgs e)
        {
            Persons.Push(GetNewPerson("Real person"));
        }

        private void PopButton_OnClick(object sender, RoutedEventArgs e)
        {
            Persons.Pop();
        }

        private void ViewButton_OnClick(object sender, RoutedEventArgs e)
        {
            StackView.Text = "View:\r\n" + 
                string.Join("\r\n", _personsView.Cast<Person>()) +
                "\r\nStack:\r\n" +
                string.Join("\r\n", Persons);
        }

        private void FakePushButton_OnClick(object sender, RoutedEventArgs e)
        {
            Persons.FakePush(GetNewPerson("Fake person"));
        }

        private Person GetNewPerson(string name)
        {
            var person = new Person
            {
                Name = name + " " + _count,
                Age = _count
            };
            _count++;
            return person;
        }

        private void ClearButton_OnClick(object sender, RoutedEventArgs e)
        {
            Persons.Clear();
        }
    }
}

что собственно меня смутило.
Если вы посмотрите на метод FakePush() из класса ObservableStack, то там реального добавления элемента в стек нет, если только возбуждение события на добавление элемента к коллекции. При этом в самом UI элемент который мы передали в событие появляется.
Совершенно не важно добавляем мы в модели представления в коллекцию\стек\что угодно какой то объект или не добавляем. Достаточно просто передать его событии CollectionChanged, что мы добавляем этот элемент.
После чего элемент начинает отображаться в ItemsControl, при это виртуализация О_о отлично работает.

Если получить View'у этой коллекции, то она содержит все элементы (реальные и "фейковые") при этом имеет собственный список этих элементов, а не просто перечислитель какой то коллекции...
1 сен 15, 11:20    [18094298]     Ответить | Цитировать Сообщить модератору
 Re: Когнитивный диссонанс  [new]
Сон Веры Павловны
Member

Откуда:
Сообщений: 6027
Реальный возвращаемый тип ICollectionView _personsView - это MS.Internal.Data.EnumerableCollectionView из PresentationFramework. Этот класс - прямой наследник CollectionView, и в своем конструкторе передает IEnumerable source (т.е. ваш ObservableStack<T>) в конструктор родителя (CollectionView), где source проверяется на предмет реализации INotifyCollectionChanged, при положительном результате проверки (и еще нескольких) на source навешивается NotifyCollectionChangedEventHandler, в обработчике которого вызывается виртуальный метод CollectionView.ProcessCollectionChanged, переопределенный в EnumerableCollectionView, и в этом переопределенном методе как раз заполняется внутренняя копия source типа ObservableCollection<object> (см. здесь). Эта копия (завернутая в ListCollectionView) и служит источником данных для всех методов и свойств реализации ICollectionView.
Проверка:
private void FakePushButton_OnClick(object sender, RoutedEventArgs e)
{
  Persons.FakePush(GetNewPerson("Fake person"));
  Console.WriteLine((
    _personsView.GetType()
    .GetField("_snapshot", BindingFlags.NonPublic | BindingFlags.Instance)
    .GetValue(_personsView) as IEnumerable<object>
    ).Count());
}

- с каждым нажатием на кнопку консольный вывод будет инкрементироваться.
1 сен 15, 12:33    [18094742]     Ответить | Цитировать Сообщить модератору
Все форумы / WPF, Silverlight Ответить