Introduction
Allowing the user of your oh-so-fancy GUI to customize the experience by the use of settings is a very nice feature for any program. However, the user expects all settings to take effect immediately, which means that you have to listen to multiple events (or, alternatively, use data binding). The user also expects all settings to be there the next time he or she opens the program, which means that you also have to store settings in the registry or to an XML-file.
If you feel like I do - that the solutions using data binding and settings are awkward to use - then you might already have found the excellent code by circumpunct. This is a rather large extension of circumpunct's work, in that it allows you to connect a control, a property and an entry in an XML-file. Any change to the value of a control or of a property is immediately mirrored in the bound entities.
(If you are familiar with WPF, you can view AutoSettings as a way of binding the DataContext
and BindingPath
of a control to a property in .NET 2.0.)
Using the Code
Using the code is very straight-forward. There are several public
methods, but the only one that you need to worry about is ConnectSetting
:
public bool ConnectSetting
(Control control, string xPath, object propertyContainer, string propertyName)
You specify the control to bind to, the XML-path to store the value under, the object that contains the property, and the name of the property that should hold the value:
mySettings = new AutoSettings.AutoSettings();
myProperties = new PropertyContainer();
mySettings.ConnectSetting(cbBlack, "isBlack", myProperties, "IsBlack");
mySettings.ConnectSetting(tbText, "text", myProperties, "Text");
mySettings.ConnectSetting(cbOptions, "optionText", myProperties, "OptionText");
mySettings.ConnectSetting(nudA, "numberA", myProperties, "NumberA");
ConnectSetting
checks if the XML-path exists in the settings file. If it does, that value will be read and written to the control and the property. If the path does not exist, it will be created. It is important to realize that control
, xPath
and the propertyContainer
/propertyName
combo can all be null
. Thus, if you do not need to reflect the values of a control into a property, set propertyContainer
and propertyName
to null
:
mySettings.ConnectSetting(rbRed, "isRed", null, null);
If you do not have a control, but still wish to bind a property to a setting, set control
to null
:
mySettings.ConnectSetting(null, "numberB", myProperties, "NumberB");
Finally, if you do not want to store the setting to file, but nevertheless wish to bind a control to a property, set xPath
to null
:
mySettings.ConnectSetting(rbYellow, null, myProperties, "IsYellow");
The choice is yours.
ConnectSetting
checks if the propertyContainer
implements INotifyPropertyChanged
. If it does, it listens to this event and updates the control and settings file whenever the property changes. This allows you to programmatically change a value of a property, without having to worry about explicitly updating the control and settings file. Thus, if PropertyContainer
looks like this:
public partial class PropertyContainer : INotifyPropertyChanged
{
private decimal myNumberA;
public decimal NumberA
{
get { return myNumberA; }
set
{
myNumberA = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("NumberA"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can simply do:
myProperties.NumberA++;
This will update the property NumberA
, the control bound to this property (nudA
) and the contents of the settings file ("numberA"
).
How It Works
While easy to use, there is a lot going on behind the scenes. For example, how is the XML-string
converted into a proper value? For every value stored in the settings file, there is also a type. This allows the XML-reader to find a proper converter and convert the value into a type, like this:
private T ConvertFromString<t>(string value) where T : IConvertible
{
if (string.IsNullOrEmpty(value)) return default(T);
return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(value);
}
As has already been hinted at, the properties are read and updated by using Reflection. For example:
PropertyInfo propertyInfo = propertyContainer.GetType().GetProperty(propertyName);
if (propertyInfo != null)
{
propertyInfo.SetValue(propertyContainer, GetSetting(xPath), null);
}
For further details, see the code or ask a question in the message area below.
Limitations
The code is light-weight, and does little or no error checking for corrupt settings files.
The code supports five types of controls: CheckBox
, RadioButton
, TextBox
, NumericUpDown
and ComboBox
. It should be fairly clear how to do the extensions to other types of controls. Let me know if you have a specific control that you want to see support for.
The "binding" is used only for the default use of the control, i.e. for a CheckBox
, the value stored is the Checked
property. There might be other properties of the controls that might be of interest to store as well, but this is not implemented. It should also be noted that AutoSettings does not use Invoke
to update the control, which means that you (in most cases) cannot update a property from a thread other than the GUI thread. This will be fixed in a future version.
History
- 2007-06-16: Initial version
Christoffer is a PhD-student in Autonomous Robotics at AASS, Örebro University, Sweden.