应用的用户设置






4.95/5 (133投票s)
简化 Windows Forms 和 WPF 的 .NET 用户配置

引言
虽然只是众多要求中的一个,但软件能够记住用户输入并在重复操作时重用这些信息的能力,是直接影响用户体验的质量标准。然而,在实际应用中,实践表明 **用户设置** 的支持不足:为什么那个软件不能记住这个?
分析其 **原因**,会想到以下几点:
- 集成用户设置过于繁琐
- 现有工具对用户设置的管理支持有限
- 用户设置无法动态集成
- 程序更新时用户设置的迁移没有明确定义
本文介绍了一个组件,展示了如何轻松实现用户设置。以下 **目标** 是其开发的驱动力:
- 用户设置的简单集成:最优方案是每个设置只需一个语句
- 组件应能够支持控制台、WinForms 和 WPF 应用程序
- 必须确保与 .NET 2+ 配置的兼容性
- 标准组件应支持重复性需求,例如窗口的位置和大小,或网格列布局
- XAML 驱动的用户设置
- 跟踪设置更改
- 动态收集设置
- 新程序版本自动迁移用户设置
- 自动生成多个、依赖于上下文的配置节
- 使用属性将设置绑定到类属性
以下示例展示了该组件的简单用法
// ------------------------------------------------------------------------
class MyApplication
{
// ----------------------------------------------------------------------
public MyApplication()
{
settings = new ApplicationSettings( this ); // register class settings
} // MyApplication
// ----------------------------------------------------------------------
[PropertySetting] // register setting using an attribute
public int MyValue { get; set; }
// ----------------------------------------------------------------------
public void Execute()
{
settings.Load(); // load settings considering previous versions
...
settings.Save(); // save settings to custom configuration section
} // Load
// ----------------------------------------------------------------------
// members
private readonly ApplicationSettings settings;
} // class MyApplication
“.NET 配置简介”
自 CLR 2.0 起,.NET Framework 在 System.Configuration
中提供了丰富的配置模块。它区分程序设置和用户设置
类型 | 文件 | Location | 发生 | 用法/管理 |
Application | app.config | 程序文件夹 | 每个安装一个 | Visual Studio 项目属性:设置 |
用户 | user.config | 用户配置文件目录 | 每个用户一个 | 派生自 ApplicationSettingsBase |
使用场景决定选择哪种类型的设置
标准/要求 | 应用程序设置 | 用户设置 |
所有用户的设置相同(例如,数据库连接) | x | |
每个用户的设置可能不同(例如,主题) | x | |
临时/本地设置(例如,窗口的位置和大小) | x | |
存储用户输入或选择(例如,文本对齐) | x | |
需要细粒度地存储设置(例如,每个插件) | x |
该矩阵表明,在许多场景下,**用户设置** 的使用是更可取的!
通过继承 ApplicationSettingsBase
类来控制用户设置。可以通过属性和属性特性定义单个设置值。Reload()
、Reset()
、Save()
和 Upgrade()
方法决定运行时行为。下图展示了 .NET 用户配置的 **数据流**

'默认值'通过 DefaultSettingValueAttribute
属性特性定义。'初始值'由 .NET Framework 控制。通过标记了 UserScopedSettingAttribute
的属性访问'会话值',该属性又使用 ApplicationSettingsBase.Item
。
Reload()
和 Reset()
方法分别支持重新加载所有设置并将其恢复到其“默认值”。
执行第一次 Save()
时,所有用户设置都会存储在 XML 文件 *user.config* 中。在随后的每次程序启动时,.NET Framework 会自动重新加载它们(**启动** 操作)。该配置文件位于何处受多种因素影响:
- 配置文件目录:本地或漫游配置文件目录(例如,*C:\Documents and Settings\MyName*)
- 公司名称:*AssemblyInfo.cs* 中
AssemblyCompanyAttribute
的值 - 应用程序名称:*AssemblyInfo.cs* 中
AssemblyProductAttribute
的值 - 证据类型和证据哈希:从应用程序域证据派生的信息
- 版本:*AssemblyInfo.cs* 中
AssemblyVersionAttribute
的值
如果这些因素中的任何一个发生变化,用户设置将被存储(并查找)在不同的文件夹中。Upgrade()
方法为从先前版本迁移用户设置提供了一些支持。但是,在更改公司名称或应用程序名称时应谨慎,因为这些更改将阻止将来的升级。
.NET 用户设置配置能够将多个 ApplicationSettingsBase
的值存储在同一个文件中。SettingsKey
作为分组机制,用于在 XML 文件中将它们分隔开
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup,
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="Itenso.Configuration.WindowSettings.MySettings1"
type="System.Configuration.ClientSettingsSection, System,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
allowExeDefinition="MachineToLocalUser" requirePermission="false" />
<section name="Itenso.Configuration.WindowSettings.MySettings2"
type="System.Configuration.ClientSettingsSection, System,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<userSettings>
<Itenso.Configuration.WindowSettings.MySettings1>
<setting name="MySetting1" serializeAs="String">
<value>Value1</value>
</setting>
</Itenso.Configuration.WindowSettings.MySettings1>
<Itenso.Configuration.WindowSettings.MySettings2>
<setting name="MySetting2" serializeAs="String">
<value>Value2</value>
</setting>
</Itenso.Configuration.WindowSettings.MySettings2>
</userSettings>
</configuration>
Client Settings FAQ 提供了更多关于 .NET 配置的有趣见解。文章 使用 .NET 2.0 读取/写入 App.Config 文件 演示了应用程序设置的管理。
扩展的 .NET 用户设置
为了实现期望的改进,用户设置已从 ApplicationSettingsBase
中分离出来。新的 Setting
元素将设置值绑定到一个可定义的源。提供以下 Setting
类:
类 | 技术栈 | 函数 |
设置 |
控制台、WinForms、WPF | 保存/加载,以及与 System.Configuration 用户设置的集成 |
ValueSetting |
控制台、WinForms、WPF | 无绑定的设置值 |
FieldSetting |
控制台、WinForms、WPF | 绑定到类字段 |
PropertySetting |
控制台、WinForms、WPF | 绑定到类属性 |
DataGridViewSetting |
WinForms | DataGridView 列设置 |
DependencyPropertySetting |
WPF | 绑定到依赖属性 |
ListViewSetting |
WPF | ListView 列设置 |
Setting
类作为所有设置的基类。ValueSettingBase
类承担简单基于值的设置的角色。
下图展示了这些扩展的用户设置如何影响数据流

Load()
将用户设置传输到 Setting
。通过将升级功能集成到 Load()
中,我们实现了用户设置的自动升级。Reload()
和 Reset()
操作将“设置值”与“会话值”同步。Save()
使用“设置值”作为其源。具有绑定(如 PropertyBinding
)的设置将使用类属性值。
Setting.HasChanged
属性指示“设置值”相对于“会话值”是否已更改。这可用于确认设置的保存。
为了实现运行时行为的自动化,提供了各种 ApplicationSettingsBase
的派生类:
类 | 技术栈 | 函数 |
ApplicationSettings |
控制台、WinForms、WPF | 设置管理、保存/加载、自动升级 |
ControlSettings |
WinForms | 加载/保存 Control |
FormSettings |
WinForms | 加载/保存 Form 的 Location /Size /WindowState |
WindowApplicationSettings |
WPF | 加载/保存 Application |
FrameworkElementSettings |
WPF | 加载/保存 FrameworkElement |
WindowSettings |
WPF | 加载/保存 Window 的 Location /Size /WindowState |
ApplicationSettings
类是所有设置的基类。
SettingCollector
元素允许动态合并设置。提供以下 SettingCollector
类:
类 | 技术栈 | 函数 |
PropertySettingCollector |
WinForms、WPF | 绑定到子类属性 |
DependencyPropertySettingCollector |
WPF | 绑定到子类依赖属性 |
SettingCollector
遍历 UI 元素层次结构,并将属性设置注册到所有类的实例。例如,将 PropertySettingCollector
应用于 CheckBox.IsChecked
将存储所有复选框的状态到用户设置中。SettingCollector.CollectingSetting
事件允许单独控制每个设置的注册。出于性能考虑,SettingCollector
不应组合超过 10 个设置。
Using the Code
控制台应用程序
用户设置的支持通过 ApplicationSettings
类实现
// ------------------------------------------------------------------------
class Program
{
// ----------------------------------------------------------------------
[PropertySetting( DefaultValue=-1 )]
public int StatusCode { get; set; }
// ----------------------------------------------------------------------
public void Execute()
{
ApplicationSettings applicationSettings = new ApplicationSettings( this );
applicationSettings.Load();
Console.Write( "Please enter a number: " );
statusCode = int.Parse( Console.ReadLine() ); // modifying the field value
applicationSettings.Save();
} // Execute
// ----------------------------------------------------------------------
static void Main()
{
new Program().Execute();
} // Main
} // class Program
输入值 22 会导致以下用户配置
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="userSettings">
<section name="Itenso.Configuration.ApplicationSettings.Program"
type="System.Configuration.ClientSettingsSection, System,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<userSettings>
<Itenso.Configuration.ApplicationSettings.Program>
<setting name="StatusCode" serializeAs="String">
<value>22</value>
</setting>
<setting name="UpgradeSettings" serializeAs="String">
<value>False</value>
</setting>
</Itenso.Configuration.ApplicationSettings.Program>
</userSettings>
</configuration>
UpgradeSettings
的含义稍后会显示。
WinForms 应用程序
以下示例展示了如何实现自定义应用程序或窗体设置

控件设置
ControlSettings
类支持处理 Control
及其派生类的用户设置
// ------------------------------------------------------------------------
public class MyListBox : ListBox
{
// ----------------------------------------------------------------------
public MyListBox()
{
if ( DesignMode )
{
return;
}
ControlSettings controlSettings = new ControlSettings( this );
controlSettings.Settings.Add(
new PropertySetting( // bind setting to the property
this, // source component
"SelectedIndex" ) ); // property name
} // MyListBox
} // class MyListBox
窗体设置
Windows Form
及其派生类的用户设置由 FormSettings
类控制
// ------------------------------------------------------------------------
public partial class MyForm : Form
{
// ----------------------------------------------------------------------
public MyForm()
{
InitializeComponent();
FormSettings formSettings = new FormSettings( this );
} // MyForm
} // class MyForm
FormSettings
自动存储窗口的位置、大小和状态。FormSettings.UseLocation
等多个开关允许您自定义自动行为。特别有趣的是 FormSettings.AllowMinimized
(默认值:false
)开关,它控制窗口在最小化状态下的存储行为。
DataGridView 设置
使用 DataGridViewSetting
类可以将 DataGridView
列的位置(顺序)和宽度存储在用户设置中
// ------------------------------------------------------------------------
public partial class MyForm : Form
{
// ----------------------------------------------------------------------
public MyForm()
{
InitializeComponent();
FormSettings formSettings = new FormSettings( this );
formSettings.Settings.Add( new DataGridViewSetting( myDataGridView ) );
} // MyForm
} // class MyForm
DataGridView
示例还包含保存确认的演示。
收集的设置
以下示例演示了如何存储 Form
中所有 CheckBox
的状态值
// ------------------------------------------------------------------------
public partial class MyForm : Form
{
// ----------------------------------------------------------------------
public MyForm()
{
InitializeComponent();
FormSettings formSettings = new FormSettings( this );
formSettings.CollectingSetting +=
new SettingCollectorCancelEventHandler( FormSettingsCollectingSetting );
formSettings.SettingCollectors.Add( new PropertySettingCollector
( this, typeof( CheckBox ), "Checked" ) );
} // MyForm
// ----------------------------------------------------------------------
private void FormSettingsCollectingSetting
( object sender, SettingCollectorCancelEventArgs e )
{
if ( e.Element == this.myCheckBox ) // exclude this checkbox
{
e.Cancel = true;
}
} // FormSettingsCollectingSetting
} // class MyForm
WPF - 代码隐藏
随附的示例演示了如何实现自定义应用程序或窗口设置

DerivedSettingsWindow.xaml 使用 BaseWindow
来演示如何构建 Window
继承层次结构。通过将 WindowSettings
包含在基类 BaseWindow
中,所有派生类和 XAML 实例都将自动存储位置、大小和状态。
FrameworkElement 设置
FrameworkElementSettings
类支持处理 FrameworkElement
及其派生类的用户设置
// ------------------------------------------------------------------------
public class MyListBox : ListBox
{
// ----------------------------------------------------------------------
public MyListBox()
{
if ( DesignerProperties.GetIsInDesignMode( this ) )
{
return;
}
FrameworkElementSettings listBoxSettings = new FrameworkElementSettings( this );
listBoxSettings.Settings.Add(
new DependencyPropertySetting( // bind setting to the dependency-property
this, // source component
SelectedIndexProperty ) ); // dependency-property name
} // MyListBox
} // class MyListBox
与 WinForm 版本相比,WPF 提供了一种优雅的方式,可以使用 DependencyProperty
将 Setting
绑定到属性。无效的绑定将在编译时被识别。
窗口设置
Window
及其派生类的设置由 WindowSettings
类控制
// ------------------------------------------------------------------------
public partial class MyWindow : Window
{
// ----------------------------------------------------------------------
public MyWindow()
{
WindowSettings windowSettings = new WindowSettings( this );
} // MyWindow
} // class MyWindow
WindowSettings
自动存储窗口的位置、大小和状态。FormSettings.UseLocation
、FormSettings.AllowMinimized
等开关控制保存行为(参见上文)。
ListView 设置
使用 ListViewSetting
类可以将 ListView
列的位置(顺序)和宽度存储在用户设置中
// ------------------------------------------------------------------------
public partial class MyWindow : Window
{
// ----------------------------------------------------------------------
public MyWindow()
{
WindowSettings windowSettings = new WindowSettings( this );
windowSettings.Settings.Add( new ListViewSetting( myListView ) );
} // MyWindow
} // class MyWindow
ListView
示例还包含保存确认的演示。文章 ListView 布局管理器 演示了如何限制 ListView
的列宽以及如何支持比例列宽。
收集的设置
以下示例展示了如何存储 Window
中所有 CheckBox
的状态值
// ------------------------------------------------------------------------
public partial class MyWindow : Window
{
// ----------------------------------------------------------------------
public MyWindow()
{
WindowSettings windowSettings = new WindowSettings( this );
windowSettings.CollectingSetting +=
new SettingCollectorCancelEventHandler( WindowSettingsCollectingSetting );
windowSettings.SettingCollectors.Add( new DependencyPropertySettingCollector
( this, CheckBox.IsCheckedProperty ) );
} // MyWindow
// ----------------------------------------------------------------------
private void WindowSettingsCollectingSetting
( object sender, SettingCollectorCancelEventArgs e )
{
if ( e.Element == this.myCheckBox ) // exclude this checkbox
{
e.Cancel = true;
}
} // WindowSettingsCollectingSetting
} // class MyWindow
WPF - XAML
用户设置可以如下在 XAML 中声明:
<Window
x:Class="Itenso.Solutions.Community.ConfigurationWindowsDemo.XamlUserSettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:config="clr-namespace:Itenso.Configuration;
assembly=Itenso.Configuration.Windows"
config:WindowSettings.Settings="XamlWindowSettings">
<StackPanel>
<CheckBox
x:Name="MyOption"
Content="My Option"
config:DependencyPropertySetting.Property=
"{x:Static CheckBox.IsCheckedProperty}" />
</StackPanel>
</Window>
config:WindowSettings.Settings
属性将 WindowSetting
分配给窗口。窗口的位置/大小/状态的存储是自动的。升级时,先前版本的值也会自动迁移。
config:DependencyPropertySetting.Property
属性导致将 CheckBox.IsChecked
视为用户设置,因此会被存储。
重要提示:为了存储属性值,该元素必须在其 x:Name
中有一个值。
上面的示例会导致以下配置数据
<Itenso.Configuration.WindowSettings.XamlWindowSettings>
<setting name="Window.Top" serializeAs="String">
<value>203</value>
</setting>
<setting name="Window.Height" serializeAs="String">
<value>200</value>
</setting>
<setting name="Window.Left" serializeAs="String">
<value>813</value>
</setting>
<setting name="Window.Width" serializeAs="String">
<value>713</value>
</setting>
<setting name="Window.WindowState" serializeAs="String">
<value>Normal</value>
</setting>
<setting name="UpgradeSettings" serializeAs="String">
<value>False</value>
</setting>
<setting name="MyOption.IsChecked" serializeAs="String">
<value>True</value>
</setting>
</Itenso.Configuration.WindowSettings.XamlWindowSettings>
ListView 设置
使用 XAML,ListView
列的位置(顺序)和宽度可以如下存储在用户设置中:
<Window
x:Class="Itenso.Solutions.Community.ConfigurationWindowsDemo.XamlUserSettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:config="clr-namespace:Itenso.Configuration;
assembly=Itenso.Configuration.Windows"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
config:WindowSettings.Settings="XamlWindowSettings">
<StackPanel>
<ListView
Name="MyListView"
ItemsSource="{Binding MyList}"
config:ListViewSetting.Settings="MyListView">
...
</ListView>
</StackPanel>
</Window>
config:ListViewSetting.Settings
属性定义了设置的名称。必须将 config:WindowSettings.Settings
分配给 Window
。
收集的设置
以下示例展示了 XAML 中 SettingCollector
的用法
<Window
x:Class="Itenso.Solutions.Community.ConfigurationWindowsDemo.XamlUserSettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:config="clr-namespace:Itenso.Configuration;
assembly=Itenso.Configuration.Windows"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
config:WindowSettings.Settings="XamlWindowSettings"
config:WindowSettings.CollectedSetting="{x:Static CheckBox.IsCheckedProperty}">
<StackPanel>
<CheckBox
x:Name="MyOption"
Content="My Option"
config:WindowSettings.ExcludeElement="True" />
</StackPanel>
</Window>
config:WindowSettings.CollectedSetting
注册了一个 DependencyPropertySetting
。这只考虑包含已定义 x:Name
的元素。使用 config:WindowSettings.ExcludeElement
可以将元素排除在注册之外。
关注点
- 示例应用程序演示了如何为每个窗体/窗口实现“另存为默认值”功能
- 为
FieldSetting
或PropertySetting
指定无效名称将导致异常 FieldSetting
的readonly
字段将导致异常PropertySetting
:未分配get; set;
访问器的属性将导致异常UpgradeSettings
设置的默认值为true
,在升级后将设置为false
Setting
通过ValueSaving
和ValueLoading
事件提供运行时控制ApplicationSettings.UseAutoUpgrade
控制自动升级行为ApplicationSettings
使用LocalFileSettingsProvider
- 可以使用
ValueSettingBase.SerializeAs
属性指定Setting
的序列化格式。有关如何以二进制格式序列化设置的示例,请参见DataGridViewSetting
和ListViewSetting
- 可以使用
FormSettings.SaveCondition
和WindowSettings.SaveCondition
属性来影响存储行为,从而使其符合DialogResult
Setting.CreateSettingProperty()
方法演示了如何动态包含SettingsProperty
WindowSettings.OnWindowSettingsChanged
和DependencyPropertySetting.FindApplicationSettings
演示了如何动态地将一个属性(DependencyPropertySetting.ApplicationSettingsProperty
)分配给一个对象并进行评估。这种情况使用该属性来确定应该将哪个ApplicationSettings
分配给DependencyPropertySetting
DepedencyPropertySetting
将分配给在父层次结构中找到的第一个ApplicationSettings
。这可以是WindowSettings
或FrameworkElementSettings
历史
- 2013 年 9 月 5 日 - v1.2.0.0
DependencyPropertySetting
:修复了GetApplicationSettings
和SetApplicationSettings
- 感谢 Victor_E
- 2013 年 8 月 27 日
- 已将二进制文件添加到下载中
- 2012 年 4 月 12 日 - v1.1.0.0
- 修复了小问题
- 重构了源代码
- 增强了 WPF 示例
- 修复了文章格式
- 2011 年 2 月 15 日
- 增强了对多显示器环境中最大化窗口的支持 - 感谢 Ryan
- 添加了 Visual Studio 2010 的项目和解决方案
- 重构了代码 - 或者更确切地说“重构”
- 2009 年 3 月 18 日
ApplicationSettings
:添加了新的事件SettingSaving
和SettingLoading
SettingValueEventArgs
和SettingValueCancelEventArgs
:新属性TargetValue
,允许在保存/加载事件中更改设置值FormSettings
和WindowSettings
:所有设置均公开可用
- 2009 年 1 月 15 日
- 添加了
FieldSettingAttribute
和PropertySettingAttribute
- 重构了组件以使用自动属性
- 添加了带属性设置的示例
- 添加了
- 2008 年 5 月 20 日
- 收集设置:新的类
PropertySettingCollector
和DependencyPropertySettingCollector
- 跟踪设置更改:新属性
Setting.HasChanged
和SettingCollection.HasChanges
- WinForms:根据
Form.DialogResult
保存设置,新属性FormSettings.SaveCondition
- WPF:根据
Window.DialogResult
保存设置,新属性WindowSettings.SaveCondition
- 错误处理:新属性
Setting.ThrowOnErrorSaving
和Setting.ThrowOnErrorLoading
- 收集设置:新的类
- 2008 年 5 月 10 日
- WinForms:新设置
DataGridViewSetting
- WPF:新设置
ListViewSetting
- WinForms 示例应用程序:关于颜色和字体设置的新示例
- WPF 示例应用程序:关于基于自定义依赖属性的颜色设置的新示例
- 添加了文章链接 使用 .NET 2.0 读取/写入 App.Config 文件
- 添加了文章链接 ListView 布局管理器
- WinForms:新设置
- 2008 年 5 月 3 日
- 首次公开发布