Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > WPF и Silverlight > Обновление ListBox после десериализации


Автор: dAlex 13.5.2011, 10:26
При изменении данных в самой программе ListBox'ы обновляются нормально (добавление item'ов). А если делать десериализацию, то данные в коллекциях появляются, а в связанных с ними листбоксах нет.

Код

//пробовала делать и так
CclistBox.Items.Refresh();
//и так
CclistBox.UpdateLayout();


описаны так
Код

<ListBox Name="CclistBox" ItemsSource="{Binding Cc_collection}" />


Код

public ObservableCollection<double> Cc_collection
{
    get { return Core.Cc_collection; }
    set
    {
        Core.Cc_collection = value;
        NotifyPropertyChanged("Cc_collection");
    }
}   

Автор: jonie 13.5.2011, 11:46
а как вы делаете десериализацию? При ней создается новый объект, а у вас биндинг на Cc_collection остается таким какой был наверняка..

Автор: dAlex 13.5.2011, 11:55
у меня создается новый объект Core, в котором все хранится. Обычные переменные десереализуются на ура и в текстбоксах появляются =)

Автор: dAlex 13.5.2011, 12:41
а с текстбоксами я делаю так

Код

Chbox.GetBindingExpression(TextBox.TextProperty).UpdateTarget();


и все обновляется

или надо после десериализации очистить листбокс и добавить туда заново все айтемы?

Автор: Brilona 13.5.2011, 14:28
Я могу ошибаться, но думаю, что нужно использовать NotifyCollectionChanged вместо NotifyPropertyChanged.

Автор: dAlex 13.5.2011, 14:38
пробую =) делаю по аналогии с propertyChanged

Код

private void NotifyCollectionChanged(string info) //что нужно писать в параметре
{
    if (_collectionChanged != null)
           _collectionChanged(this, new CollectionChangeEventArgs(CollectionChangeAction.Refresh, info); //и, соответственно, во втором параметре здесь?
}   



Автор: Kefir 13.5.2011, 15:27
dAlex, коллекция, к которой привязываются листбоксы должна реализовывать INotifyCollectionChanged (например ObservableCollection).

Автор: Brilona 13.5.2011, 16:42
Код

public class ItemCollection : INotifyCollectionChanged
{
    ...
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    
    protected void OnCollectionChanged()
    {
        if (CollectionChanged != null)
        {
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }
    ...
}

Автор: dAlex 16.5.2011, 09:17
Всё равно, изначальная коллекция также обновляется, как и было, а свойства - нет

Код

public event NotifyCollectionChangedEventHandler _collectionChanged;
event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged
{
    add { _collectionChanged += value; }
    remove { _collectionChanged -= value; }
}        
private void NotifyCollectionChanged()
{
    if (_collectionChanged != null)
        _collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace));
}   
public ObservableCollection<double> Cc_collection
{
    get { return Core._Cc_collection; }
    set
    {
        Core._Cc_collection = value;
        NotifyCollectionChanged();
    }
}

Core._Cc_collection обновляется, Cc_collection остается пустым

Автор: -Mikle- 16.5.2011, 10:04
После десериализации вызовите событие PropertyChanged для свойства "Cc_collection".
CollectionChanged используется, если меняется коллекция в существующем экземпляре коллекции.

Автор: dAlex 16.5.2011, 10:25
Цитата(-Mikle- @  16.5.2011,  11:04 Найти цитируемый пост)
CollectionChanged используется, если меняется коллекция в существующем экземпляре коллекции. 

это как? например, у меня есть какие-то данные, но нужно их изменить на десериализуемые, это и есть изменение коллекции в существующем экземпляре?

Автор: -Mikle- 16.5.2011, 12:38
Нет. Это, например, вы десериализовали (или создали) коллекцию. Затем, при нажатии на кнопку (или еще как-нить) вызываете метод Add (AddRange, Insert, Clear, Remove и т.д.) у коллекции. То есть это когда меняется существующая (уже забинденная) коллекция. Другое дело, если в какой-то момент создается новая коллекция (new) и затем присваивается свойству. В этом случае оповещение должно происходить об изменении свойства (в вашем случае Cc_collection), так как с логической точки зрения коллекция не изменялась (изменилось свойство, которое будет ссылаться на новый экземпляр).

Еще одно важное замечание: любое событие должно вызываться от имени самого объекта, а не от чужого. Биндинги следят за этим и если экземпляр не тот, то игнорируют любое событие об изменении (CollectionChanged, PropertyChanged и т.д.).

В заключении, предлагаю вам новое решение:
1) Уберите все свойства типа Cc_collection (которые просто делегируют значения свойству Core).
2) Оставьте (или объявите, если еще у вас нет) только свойство Core (открытое). Если я правильно понял, то именно ему вы присваиваете результат десериализации, то есть оно у вас рутовое (если нет, поправьте).
3) В его сетере пропишите вызов события PropertyChanged - OnPropertyChanged("Core")
4) Во всех диндингах, вместо, например, ItemsSource="{Binding Cc_collection}" пишите  ItemsSource="{Binding Path=Core.Cc_collection}"

Что получилось теперь, после того как вы десериализуете ваш рутовый объект и присвоите его свойству Core, то автоматически вызовется событие PropertyChanged (пункт 3) и все биндинги обновятся, так как в путях биндингов будет присутствовать часть Core.

Автор: dAlex 16.5.2011, 13:22
Сделала свойство Core, забиндила на него, прописала ему OnPropertyChanged, но все равно после десериализации пустота. Наверное, дело в
Цитата(-Mikle- @  16.5.2011,  13:38 Найти цитируемый пост)
любое событие должно вызываться от имени самого объекта, а не от чужого

а как вызвать OnPropertyChanged от имени самого объекта?
И еще -  обычные переменные в текстбоксах появляются, почему?

Автор: -Mikle- 16.5.2011, 14:06
Ох... не видя кода и имея только общее представление о том что у вас, трудно даже пример привести...
Ну ок...

Имеем наш объект:
Код

    public class MyRootData : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private int _myProperty;
        public int MyProperty
        {
            get { return _myProperty; }
            set
            {
                if (_myProperty == value) return;
                _myProperty = value;
                OnPropertyChanged("MyProperty");
            }
        }

        private ObservableCollection<double> _cc_collection;
        public ObservableCollection<double> Cc_collection
        {
            get { return _cc_collection; }
            set
            {
                if (_cc_collection == value) return;
                _cc_collection = value;
                OnPropertyChanged("Cc_collection");
            }
        }
    }


Имеем виндоу:
Код

<Window x:Class="WpfApplication4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <TextBox Text="{Binding Path=Core.MyProperty}" />

        <ListBox ItemsSource="{Binding Path=Core.Cc_collection}" MinHeight="50"/>

        <Button Content="Load Data" Click="Button_Click" />
        <Button Content="Add item to list box" Click="Button_Click_1" />
        <Button Content="Change MyProperty" Click="Button_Click_2" />
    </StackPanel>
</Window>


И имеем код бехинд такой к нему:
Код

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this; //это для примера
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private MyRootData _core;
        public MyRootData Core
        {
            get { return _core; }
            set
            {
                if (_core == value) return;
                _core = value;
                OnPropertyChanged("Core");
            }
        }

        private void LoadCore()
        {
            // тут можно десериализовать Core.
            // для примера, я его просто создам вручную, так как разницы абсолютно нет.

            var core = new MyRootData();
            core.MyProperty = 35;
            core.Cc_collection = new ObservableCollection<double> { 2.4, 0.788, 3.55 };

            this.Core = core;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            LoadCore();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            if (Core == null)
                LoadCore();
            Core.Cc_collection.Add(new Random().NextDouble());
        }

        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            if (Core == null)
                LoadCore();
            Core.MyProperty = new Random().Next();
        }
    }


Тут все работает как надо. Свертесь со своим кодом по этому примеру. Надеюсь, что это поможет.

Автор: dAlex 16.5.2011, 14:35
хм, то есть я зря свойства все перенесла в MyRootData, надо будет исправить...
Сделала как вы сказали, наконец-то свойство Cc_collection обновляется =) но листбокс на форме почему-то все равно пустует 

Код

public class Window1 : INotifyPropertyChanged
{
    private PropertyChangedEventHandler _propertyChanged;
    event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
    {
        add { _propertyChanged += value; }
        remove { _propertyChanged -= value; }
    }
            
    private RobotsBrain Core;
    public RobotsBrain pCore //пришлось пока так назвать, Core используется много где
    {
        get { return Core; }
        set
        {
            if (Core == value) return;
            OnPropertyChanged("pCore");
        }
    }

    void Load()
    {
        XmlSerializer serializer = new XmlSerializer(typeof(RobotsBrain));
        using (StreamReader stream = new StreamReader("appdata.xml"))
            Core = (RobotsBrain)serializer.Deserialize(stream);
    }
}


Код

public partial class RobotsBrain : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    
    .....
    
    private ObservableCollection<double> _Cc_collection = new ObservableCollection<double>();
    public ObservableCollection<double> Cc_collection
    {
        get { return _Cc_collection; }
        set
        {
            if (_Cc_collection == value) return;
            _Cc_collection = value;
            OnPropertyChanged("Cc_collection");
        }
    }
}


Код

<ListBox ItemsSource="{Binding Path=Core.Cc_collection}" />

Автор: -Mikle- 16.5.2011, 17:13
Цитата(dAlex @  16.5.2011,  17:35 Найти цитируемый пост)
то есть я зря свойства все перенесла в MyRootData

Вообще дублирование без необходимости это всегда зря.

Цитата

//пришлось пока так назвать, Core используется много где

Вот отсюда остальные проблемы. Я так понимаю у вас было поле класса с именем Core, которое использовалось везде. Значит делаете так: 
1) поле класса переименовываете в _core (без рефакторинга)
2) Свойство pCore -> Core.
Теперь, везде где использовалось поле Core, будет использоваться свойство Core и все будет в порядке с вашим кодом. Вообще старайтесь избегать обращения к полям (которые хранят значения свойств) даже изнутри класса. Лучше использовать обращение к свойствам, компиляторы уже умеют оптимизировать подобные вызовы.

Автор: dAlex 16.5.2011, 21:15
нет-нет, не так было =) изначально у меня не было поля Core, был объект Core, который использовался, и в классе окна были определены свойства полей Core, которые раньше в самом Core и находились. А когда возникла необходимость создать поле, его я назвала pCore, чтобы проверить гипотезу. Потом, как заработает, я все переименую (там просто многовато, а остальные методы нужны рабочими)

Автор: -Mikle- 16.5.2011, 23:54
Я ничего не понял, но обратите внимание, что если у вас pCore, то вы должны использовать именно его (свойство pCore, а не поле Core) во всех биндингах и в методе Load. Это важный момент, я думаю вы понимаете.

Автор: dAlex 17.5.2011, 09:47
Так. В общем, переделаю все по-нормальному, перенесу обратно свойства, сделаю как вы сказали и попробую снова =)

Автор: -Mikle- 17.5.2011, 09:54
Цитата(dAlex @  17.5.2011,  12:47 Найти цитируемый пост)
перенесу обратно свойства

Надеюсь вы правильно поняли, потому что эта фраза заставила меня засомневаться.

Автор: dAlex 17.5.2011, 10:37
переделала, все красиво и работает =)
кстати нашла ошибку в своем предыдущем коде, она ужасна
Код

public RobotsBrain pCore 
    {
        get { return Core; }
        set
        {
            if (Core == value) return;
            //а тут-то строчки не хватало
            OnPropertyChanged("pCore");
        }
    }

еще бы там пусто было =)

Цитата(-Mikle- @  17.5.2011,  10:54 Найти цитируемый пост)
Надеюсь вы правильно поняли, потому что эта фраза заставила меня засомневаться. 

да, все, как в вашем примере =)

Автор: -Mikle- 17.5.2011, 10:41
Рад, что у вас все получилось smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)