-1

I have the following:

MainWindow:

<Window x:Class="TestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestApp"
        xmlns:settings="clr-namespace:TestApp.Settings"
        mc:Ignorable="d" Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ItemsControl>
            <ItemsControl.ItemsSource>
                <Binding>
                    <Binding.Source>
                        <CollectionViewSource Source="{Binding Source={x:Static settings:CustomSettings.Default}, Path=coll}" />
                    </Binding.Source>
                </Binding>
            </ItemsControl.ItemsSource>

            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="100" />
                            <ColumnDefinition Width="100" />
                        </Grid.ColumnDefinitions>

                        <TextBox Text="{Binding Name}" />
                        <Button Grid.Column="1" Click="Button_Click" />
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

Code-Behind:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;

namespace TestApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            if (Settings.CustomSettings.Default.coll == null)
            {
                Settings.CustomSettings.Default.coll = new ObservableCollection<BasicClass>();
                Settings.CustomSettings.Default.coll.Add(new BasicClass("String1"));
                Settings.CustomSettings.Default.coll.Add(new BasicClass("String2"));
                Settings.CustomSettings.Default.coll.Add(new BasicClass("String3"));
            }

            Settings.CustomSettings.Default.Save();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Settings.CustomSettings.Default.Save();

            foreach (BasicClass item in Settings.CustomSettings.Default.coll)
            {
                MessageBox.Show(item.Name);
            }
        }
    }

    public class BasicClass : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                if (value != name)
                {
                    name = value;
                    NotifyPropertyChanged("Name");
                }
            }
        }

        public BasicClass() { }

        public BasicClass(string Name)
        {
            this.Name = Name;
        }
    }
}

Settings:

using System.Collections.ObjectModel;
using System.Configuration;
using System.Diagnostics;

namespace TestApp.Settings
{
    internal sealed partial class CustomSettings : ApplicationSettingsBase
    {
        private static CustomSettings defaultInstance = ((CustomSettings)(Synchronized(new CustomSettings())));

        public static CustomSettings Default
        {
            get
            {
                return defaultInstance;
            }
        }

        [UserScopedSetting()]
        [DebuggerNonUserCode()]
        public ObservableCollection<BasicClass> coll
        {
            get
            {
                return ((ObservableCollection<BasicClass>)(this["coll"]));
            }
            set
            {
                this["coll"] = value;
            }
        }
    }
}

How it works:

The app present three controls consisting of a TextBox and a Button. These are part of an ItemsControl whose source is bound to a user setting 'coll', of type ObservableCollection<BasicClass>. BasicClass has one property, 'Name', which appears in the TextBox via data-binding.

Expected behaviour:

I change the text in a TextBox, then click the corresponding Button. This would then save the new value in 'coll', and then present a MessageBox sequence demonstrating that this has indeed been changed. I restart the app, and my value is showing the newly saved value.

Actual behaviour:

I change the text, I click the Button, the MessageBox sequence shows me that the value is now stored in the user settings (and should therefore have been saved). However, when I restart the app, I see the original value, not the saved one.

An anomaly(?):

If I click the button twice instead of once (going through the MessageBox sequence twice), when I restart the value has now been successfully saved.

Chris Mack
  • 5,148
  • 2
  • 12
  • 29
  • Please go back and read the [mcve] page. As with your previous question, this one appears to include far more code than is actually required to reproduce your problem. For example, is it really true that this issue happens _only_ when you are using a `WrapPanel` for your `ItemsControl`? I seriously doubt it is. There are many other examples of such extraneous code in your post. Please provide a good MCVE if you want someone to help you debug your code. – Peter Duniho Aug 19 '16 at 21:43

1 Answers1

1

EDIT (original answer below):

While I suspect implementing IBindableComponent on a subclass of ObservableCollection might work, I don't recommend it. If you're just storing strings, System.Collections.Specialized.StringCollection might help you.

But in general, I don't think it makes much sense to update your application settings each time something changes. Instead, load them during application startup into your view model (e.g. an ObservableCollection) and move them back to the application settings when closing. This way, the values only need to be deserialized and serialized once.

The effect you mention in your comment about setting the value to itself works (it seems) because the list is re-serialized when you set it. It appears, ApplicationSettingsBase is storing a serialized copy of every value you provide, and therefore can't react to changes in your original object. When you provide the value again, it overwrites its copy with a serialized version of the new state of your object. However, if you serialize the list every time a user makes a change, it will impact your app's performance, once the list gets longer.

This might be interesting to you as well: How to store int[] array in application Settings

And it seems unnecessary to subclass ApplicationSettingsBase yourself, see https://social.msdn.microsoft.com/Forums/en-US/4e299ed8-8e3a-408e-b900-eb6738fe0775/persist-and-restore-application-state?forum=wpf

ORIGINAL:

I'm not familiar with the ApplicationSettingsBase, but perhaps this helps https://msdn.microsoft.com/en-in/library/8eyb2ct1(en-us).aspx:

You can only bind an application setting to a component that supports the IBindableComponent interface. Also, the component must implement a change event for a specific bound property, or notify application settings that the property has changed through the INotifyPropertyChanged interface. If the component does not implement IBindableComponent and you are binding through Visual Studio, the bound properties will be set the first time, but will not update. If the component implements IBindableComponent but does not support property change notifications, the binding will not update in the settings file when the property is changed.

It seems like it has to do with the fact that only serialized versions of your settings are stored and therefore changes within the list are not recognized (because the reference itself does not change). Note that when you replace the list entirely upon clicking a button, the new list's values are stored.

Community
  • 1
  • 1
doubleYou
  • 310
  • 1
  • 4
  • 14
  • Thanks - this seems to be right. So where would I need to implement `IBindableComponent`? On `ObservableCollection`? On `CollectionViewSource`? When you say the reference itself doesn't change, which reference are you talking about? Interestingly, adding `Settings.CustomSettings.Default.coll = Settings.CustomSettings.Default.coll;` before `Settings.CustomSettings.Default.Save();` fixes the problem, but of course it feels like a hack. – Chris Mack Aug 20 '16 at 11:10
  • @ChrisMack please view my updated answer. I think you should approach the problem a bit differently. – doubleYou Aug 20 '16 at 12:59