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

静态属性的动态绑定

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (14投票s)

2011 年 11 月 2 日

CPOL

4分钟阅读

viewsIcon

45662

downloadIcon

573

如何实现静态属性的动态绑定。

引言

在 WPF 中,有时我们希望绑定到静态值,而不是添加到与 DataContext 关联的 ViewModel 中。通常,我们希望绑定的静态值是 Settings 文件中的值。绑定到这些值没有问题;问题在于当值发生变化时;项目 Settings 不支持 INotifyPropertyChanged 接口,因此也不支持响应 PropertyChanged 事件。因此,如果属性发生更改,这些更改不会传播到 View

背景

过去,我曾将这些静态变量添加到 ViewModel 中,以便能够绑定到属性,但这些变量实际上不应成为 ViewModel 的一部分,因为它们与底层的 Model 没有关系。在我开始研究这个想法的具体案例中,我正在研究如何更好地实现一个我难以维护的代码设计。我有一组值存储在 Settings 文件中,这些值与保存的目录路径以及与目录路径恢复或选择默认目录相关的枚举相关。使之令人困惑的是,对于不同的目录路径,存在许多针对错误值的备用选项。为了提高可维护性,我并没有真正将这些信息从 ViewModel 中移除,但由于我已经在进行更改,我决定研究如何将这些信息从 ViewModel 中移除。我还想将绑定命令放在与可绑定属性相同的类中。

实现

我为本文创建的示例非常简单。它包含一个包含路径的 TextBox,以及一个按钮,让用户浏览到一个新文件夹,该文件夹将显示在 TextBox 中。

首先显而易见的是,我需要一个 Adapter 类来连接 Settings 和 View。第一个问题是我需要绑定到一个静态类,否则我需要将该类与窗口的 DataContext 关联,这无法达到目的。静态类的问题在于它无法实现 INotifyPropertyChanged。解决方案是有一个静态属性,该属性返回实现 INotifyPropertyChanged 的类的实例,这是一个单例。我使用同一个类来处理静态和实例代码。以下代码使用静态属性创建并返回一个实例

public class DirectoryManager : INotifyPropertyChanged
{
    private static DirectoryManager _instance = new DirectoryManager();

    public static DirectoryManager Instance { get { return _instance; } }

    private DirectoryManager() { }

    public event PropertyChangedEventHandler PropertyChanged;
}

请注意,我还将类的默认实例构造函数设为 private,以确保它是一个单例,当然,该类继承自 INotifyPropertyChanged 并定义了必需的 PropertyChanged 事件。

用于显示目录路径的公共属性是一个用于绑定的普通属性,并带有用于从 Settings 文件获取和保存字符串的附加代码

public string DirectoryPath
{
  get { return Properties.Settings.Default.DirectoryPath; }
  set
  {
    if (Properties.Settings.Default.DirectoryPath != value)
    {
      Properties.Settings.Default.DirectoryPath = value;
      Properties.Settings.Default.Save();
      if (PropertyChanged != null)
        PropertyChanged(this, new PropertyChangedEventArgs("DirectoryPath"));
    }
  }
}

这一切都非常直接(通常我不会在属性中直接处理 PropertyChanged 事件,但对于示例,我更喜欢这样做)。困难的部分是连接 Browse 按钮的支持。

不幸的是,无法为按钮使用 ICommand 接口;似乎 Microsoft 没有为 Source 绑定(它提供了绑定静态变量的能力)提供 Command 绑定的支持。因此,我必须改用 RoutedUICommand。这绝对比普通的 Command 绑定复杂,我并不喜欢(我希望看到一种更简单的实现方式,用于绑定事件,而不是 ICommand),但它有效,并且有一个简单的示例来演示如何实现 RoutedUICommand 并不坏。在 DirectoryManager 类中,必须包含以下代码来创建 RoutedUICommand

public static RoutedUICommand BrowseCommand
{
    get { return _browseCommand; }
}

public static void BrowseCommand_Executed(object sender, 
                   ExecutedRoutedEventArgs e)
{
    var path = DirectoryManager.Browse(_instance.DirectoryPath, 
                                       "Select Directory");
    if (path != null)
      _instance.DirectoryPath = path;
}

public static void BrowseCommand_CanExecute(object sender, 
       CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

static DirectoryManager()
{
    _browseCommand = new RoutedUICommand("Browse for Directory",
      "BrowseCommand", typeof(DirectoryManager));
}

正如所见,我们需要一个属性来返回 BrowseCommand,并在类构造函数中实例化该命令。

此外,我们需要定义两个方法来实现命令的 ExecuteCanExecute 方法。这些方法的名称并不重要,因为有另一段代码会将这些方法与命令关联起来

public static void BindCommands(System.Windows.Window window)
{
    window.CommandBindings.Add(new CommandBinding(BrowseCommand,
      BrowseCommand_Executed, BrowseCommand_CanExecute));
}

据说还有一种方法可以使用 XAML 创建绑定,但这似乎不起作用,所以我们必须在代码中进行绑定。

为了能够在窗口中使用该命令,我们需要在 Window 的代码隐藏中调用此方法

namespace BindableSettingsExample
{
    public partial class MainWindow : Window
    {
      public MainWindow()
      {
        InitializeComponent();
        DirectoryManager.BindCommands(this);
      }
    }
}

现在,可以创建 XAML,它提供了对 Settings 文件的绑定,以及 Browse 按钮的命令绑定。

<Window x:Class="BindableSettingsExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:bindableSettings="clr-namespace:BindableSettingsExample"
        Title="Bindable Settings Example" Height="300" Width="525">
  <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="300"/>
      <ColumnDefinition Width="75"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Label Grid.Column="0" Grid.Row="0" Margin="0,0,2,-5" 
              HorizontalAlignment="Left" 
              Content="Directory Path:"/>
    <TextBox Grid.Column="0" Grid.Row="1" Name="txtDirectoryPath" 
             Height="22" Margin="6,0,0,5"
             Text="{Binding DirectoryPath,Source={x:Static 
               bindableSettings:DirectoryManager.Instance}, Mode=TwoWay}" 
             IsReadOnly="True"/>
    <Button Grid.Column="1" Grid.Row="1" Content="_Browse" Height="22" 
              Padding="5,2,5,2" Margin="5,0,7,5"
              Command="bindableSettings:DirectoryManager.BrowseCommand"/>
  </Grid>
</Window>

如所见,TextBox 文本的绑定只是

{Binding DirectoryPath,Source={x:Static 
    bindableSettings:DirectoryManager.Instance}, Mode=TwoWay}

按钮命令的绑定只是

bindableSettings:DirectoryManager.BrowseCommand

结论

此实现用于绑定到 Settings 文件,但设计实际上更加灵活。该设计可以与未保存在 Settings 文件中的数据,甚至仅与会话相关的数据一起使用。还可以将静态实例更改为与应用程序状态相关的不同类。这可以在不弄乱 DataContextViewModel 的情况下完成,其中包含与 Model 无关的信息。

© . All rights reserved.