65.9K
CodeProject 正在变化。 阅读更多。
Home

应用的用户设置

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (133投票s)

2008年5月5日

CPOL

10分钟阅读

viewsIcon

579317

downloadIcon

7580

简化 Windows Forms 和 WPF 的 .NET 用户配置

User Settings Applied

引言

虽然只是众多要求中的一个,但软件能够记住用户输入并在重复操作时重用这些信息的能力,是直接影响用户体验的质量标准。然而,在实际应用中,实践表明 **用户设置** 的支持不足:为什么那个软件不能记住这个?

分析其 **原因**,会想到以下几点:

  • 集成用户设置过于繁琐
  • 现有工具对用户设置的管理支持有限
  • 用户设置无法动态集成
  • 程序更新时用户设置的迁移没有明确定义

本文介绍了一个组件,展示了如何轻松实现用户设置。以下 **目标** 是其开发的驱动力:

  • 用户设置的简单集成:最优方案是每个设置只需一个语句
  • 组件应能够支持控制台、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 用户配置的 **数据流**

User Settings Data Flow

'默认值'通过 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 类承担简单基于值的设置的角色。

下图展示了这些扩展的用户设置如何影响数据流

User Settings Extended Data Flow

Load() 将用户设置传输到 Setting。通过将升级功能集成到 Load() 中,我们实现了用户设置的自动升级。Reload()Reset() 操作将“设置值”与“会话值”同步。Save() 使用“设置值”作为其源。具有绑定(如 PropertyBinding)的设置将使用类属性值。

Setting.HasChanged 属性指示“设置值”相对于“会话值”是否已更改。这可用于确认设置的保存。

为了实现运行时行为的自动化,提供了各种 ApplicationSettingsBase 的派生类:

技术栈 函数
ApplicationSettings 控制台、WinForms、WPF 设置管理、保存/加载、自动升级
ControlSettings WinForms 加载/保存 Control
FormSettings WinForms 加载/保存 FormLocation/Size/WindowState
WindowApplicationSettings WPF 加载/保存 Application
FrameworkElementSettings WPF 加载/保存 FrameworkElement
WindowSettings WPF 加载/保存 WindowLocation/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 应用程序

以下示例展示了如何实现自定义应用程序或窗体设置

Windows Forms User Settings Sample

控件设置

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 - 代码隐藏

随附的示例演示了如何实现自定义应用程序或窗口设置

WPF Window Settings Sample

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 提供了一种优雅的方式,可以使用 DependencyPropertySetting 绑定到属性。无效的绑定将在编译时被识别。

窗口设置

Window 及其派生类的设置由 WindowSettings 类控制

// ------------------------------------------------------------------------
public partial class MyWindow : Window
{
  // ----------------------------------------------------------------------
  public MyWindow()
  {
    WindowSettings windowSettings = new WindowSettings( this );
  } // MyWindow

} // class MyWindow

WindowSettings 自动存储窗口的位置、大小和状态。FormSettings.UseLocationFormSettings.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 可以将元素排除在注册之外。

关注点

  • 示例应用程序演示了如何为每个窗体/窗口实现“另存为默认值”功能
  • FieldSettingPropertySetting 指定无效名称将导致异常
  • FieldSettingreadonly 字段将导致异常
  • PropertySetting:未分配 get; set; 访问器的属性将导致异常
  • UpgradeSettings 设置的默认值为 true,在升级后将设置为 false
  • Setting 通过 ValueSavingValueLoading 事件提供运行时控制
  • ApplicationSettings.UseAutoUpgrade 控制自动升级行为
  • ApplicationSettings 使用 LocalFileSettingsProvider
  • 可以使用 ValueSettingBase.SerializeAs 属性指定 Setting 的序列化格式。有关如何以二进制格式序列化设置的示例,请参见 DataGridViewSettingListViewSetting
  • 可以使用 FormSettings.SaveConditionWindowSettings.SaveCondition 属性来影响存储行为,从而使其符合 DialogResult
  • Setting.CreateSettingProperty() 方法演示了如何动态包含 SettingsProperty
  • WindowSettings.OnWindowSettingsChangedDependencyPropertySetting.FindApplicationSettings 演示了如何动态地将一个属性(DependencyPropertySetting.ApplicationSettingsProperty)分配给一个对象并进行评估。这种情况使用该属性来确定应该将哪个 ApplicationSettings 分配给 DependencyPropertySetting
  • DepedencyPropertySetting 将分配给在父层次结构中找到的第一个 ApplicationSettings。这可以是 WindowSettingsFrameworkElementSettings

历史

  • 2013 年 9 月 5 日 - v1.2.0.0
    • DependencyPropertySetting:修复了 GetApplicationSettingsSetApplicationSettings - 感谢 Victor_E
  • 2013 年 8 月 27 日
    • 已将二进制文件添加到下载中
  • 2012 年 4 月 12 日 - v1.1.0.0
    • 修复了小问题
    • 重构了源代码
    • 增强了 WPF 示例
    • 修复了文章格式
  • 2011 年 2 月 15 日
    • 增强了对多显示器环境中最大化窗口的支持 - 感谢 Ryan
    • 添加了 Visual Studio 2010 的项目和解决方案
    • 重构了代码 - 或者更确切地说“重构”
  • 2009 年 3 月 18 日
    • ApplicationSettings:添加了新的事件 SettingSavingSettingLoading
    • SettingValueEventArgsSettingValueCancelEventArgs:新属性 TargetValue,允许在保存/加载事件中更改设置值
    • FormSettingsWindowSettings:所有设置均公开可用
  • 2009 年 1 月 15 日
    • 添加了 FieldSettingAttributePropertySettingAttribute
    • 重构了组件以使用自动属性
    • 添加了带属性设置的示例
  • 2008 年 5 月 20 日
    • 收集设置:新的类 PropertySettingCollectorDependencyPropertySettingCollector
    • 跟踪设置更改:新属性 Setting.HasChangedSettingCollection.HasChanges
    • WinForms:根据 Form.DialogResult 保存设置,新属性 FormSettings.SaveCondition
    • WPF:根据 Window.DialogResult 保存设置,新属性 WindowSettings.SaveCondition
    • 错误处理:新属性 Setting.ThrowOnErrorSavingSetting.ThrowOnErrorLoading
  • 2008 年 5 月 10 日
  • 2008 年 5 月 3 日
    • 首次公开发布
© . All rights reserved.