Wurunduk Wurunduk - 3 months ago 21
C# Question

User settings reads wrong double type value

I am trying to make an application for editing user settings and run in to a strange problem. I am writing and reading using user settings, and it cant read one of the variables.

public MainWindow()
{
InitializeComponent();

input1.Value = Properties.Settings.Default.input1Setting;
input2.Value = Properties.Settings.Default.input2Setting;
}


private void input1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Properties.Settings.Default.input1Setting = Convert.ToDouble(input1.Value);
Properties.Settings.Default.Save();
}

private void input2_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Properties.Settings.Default.input2Setting = Convert.ToInt16(input2.Value);
Properties.Settings.Default.Save();
}

private void OnlyNumberValidation(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex("[^0-9]+");
e.Handled = regex.IsMatch(e.Text);
}


and xaml sliders

<Slider ValueChanged="input1_ValueChanged" IsSnapToTickEnabled="True" TickFrequency="0.25" Minimum="1" Maximum="50" Name="input1" PreviewTextInput="OnlyNumberValidation"/>
<Slider ValueChanged="input2_ValueChanged" IsSnapToTickEnabled="True" TickFrequency="1" Maximum="2000" Minimum="0" Name="input2" PreviewTextInput="OnlyNumberValidation"/>


In visual studio settings editor input1 set as double, input2 as int. Botch scopes are set to user.

The problem is, only slider input2 gets right value. input1 is always set to one.

I tried to add MessageBox.Show(Properties.Settings.Default.input1Setting.ToString())
on startup, and it still showed 1, but value of input1 in app.config (The one in project/bin/debug/ folder,the one in local/company/project/1.0.0/ and in visual studio app.config is 4)

upd1 Changing 4 to 4.0 did not help. Still changes to 1.

Answer

The issue the minimum value of input1 being non-zero and the ValueChanged being set in the the XAML. When the sliders are created they are value zero by default , then the the events get wired up and then they are adjusted to be within mimimum and maximum.

This means the value changes to 1 on input1 and then the event fires and saves the value to the settings. This all occurs during InitializeComponent and therefore happens before your attempt to assign input1.Value in MainWindow's constructor.

Option 1

There are a few options, first is less changes to your existing code. Remove the ValueChanged binding from the XAML.

<Slider IsSnapToTickEnabled="True" TickFrequency="0.25" Minimum="1" Maximum="50" Name="input1" PreviewTextInput="OnlyNumberValidation"/>
<Slider IsSnapToTickEnabled="True" TickFrequency="1" Maximum="2000" Minimum="0" Name="input2" PreviewTextInput="OnlyNumberValidation"/>

And add them after the InitializeComponent, even better add them after you set the values so you don't resave the existing values:

  input1.ValueChanged += input1_ValueChanged;
  input2.ValueChanged += input2_ValueChanged;

Option 2

But what might be a better option, depending on how you view things, is to bind the controls straight to the settings and then only call save on closing of the form.

Ensure you have the Settings key available by placing it in your app.xaml file. You will need to add the namespace to the Application node attributes, xmlns:properties="clr-namespace:WpfApplication1.Properties" And Inside Application.Resourses node add the properties node. Below is an example of what your app.xaml might look like (with generic namespaces):

<Application x:Class="WpfApplication1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApplication1"
             xmlns:properties="clr-namespace:WpfApplication1.Properties"
             StartupUri="MainWindow.xaml">
  <Application.Resources>
    <properties:Settings x:Key="Settings" />
  </Application.Resources>
</Application>

Now back in your MainWindow.xaml, remove the ValueChanged (as in the above example) and add a Value property binding it to settings value:

<Slider IsSnapToTickEnabled="True" TickFrequency="0.25" Minimum="1" Maximum="50" Name="input1" Value="{Binding Source={StaticResource Settings}, Path=Default.input1Setting}" PreviewTextInput="OnlyNumberValidation"/>
<Slider IsSnapToTickEnabled="True" TickFrequency="1" Maximum="2000" Minimum="0" Name="input2" Value="{Binding Source={StaticResource Settings}, Path=Default.input2Setting}" PreviewTextInput="OnlyNumberValidation"/>

And in your MainWindow.xaml.cs file you modify it to look like this:

public MainWindow()
{
  InitializeComponent();
  Closing += MainWindow_Closing;
}

private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
  Properties.Settings.Default.Save();
}

private void OnlyNumberValidation(object sender, TextCompositionEventArgs e)
{
  Regex regex = new Regex("[^0-9]+");
  e.Handled = regex.IsMatch(e.Text);
}

Of course if you prefer to wire up your events in the XAML instead you can skip the Closing += MainWindow_Closing; in the MainWindow.xaml.cs file and instead add this, Closing="MainWindow_Closing", attribute to MainWindow.xaml's Window node.

I like option 2 because it only saves once, even though the Settings.Default values are always available based on the sliders value, you only need to save it so it is available on next start up.

Try these out and see which one you like better.