在单个绑定中使用多个IValueConverter来享受数据更改通知

本文关键字:数据 通知 IValueConverter 绑定 单个 | 更新日期: 2023-09-27 17:59:52

编辑:这是问题的另一个(简单)版本:

假设我有一个带有TextBox的应用程序,用于搜索Persons(在DB、本地存储等中)。当键入一个人时,搜索就完成了,UI应该根据找到的person更新。在我的情况下,UI包含单个TextBlock,显示属性Person.ShortName的值。

为此,我需要使用具有以下思想的助手类PersonQueryCreator

public class PersonQueryCreator : DependencyObject
{
  #region Static Data Members
  public static DependencyProperty ShortNameToSearchProperty = DependencyProperty.Register("ShortNameToSearch", typeof(string), typeof(PersonQueryCreator), new PropertyMetadata(UpdateQueryObject));
  private static DependencyPropertyKey QueryPropertyKey = DependencyProperty.RegisterReadOnly("Query", typeof(string), typeof(PersonQueryCreator), new PropertyMetadata());
  public static DependencyProperty QueryProperty = QueryPropertyKey.DependencyProperty;
  #endregion
  #region Public Properties
  public string ShortNameToSearch
  {
    get { return (string)GetValue(ShortNameToSearchProperty); }
    set { SetValue(ShortNameToSearchProperty, value); }
  }
  public object Query
  {
    get { return (string)GetValue(QueryProperty); }
  }
  #endregion
  #region Public Methods
  public static IPerson SearchForPerson(object query)
  {
    ...
  }
  #endregion
}

这个类有一些属性,我可以设置它们来创建查询(在本例中,我将使用PersonQueryCreator.ShortNameToSearch),它生成一个查询对象,该对象在属性PersonQueryCreator.Query中返回。这个类还提供了一个静态方法PersonQueryCreator.SearchForPerson,它接受一个查询参数并返回一个Person对象。

因此,我必须使用Converter在TextBlock中显示合适的人员的ShortName。我可以将短名称TextBlock直接绑定到搜索TextBox,并使用转换器查询和返回ShortName属性。但是,如果我要这样做,那么如果Person.ShortName内容发生更改(而不更改Person对象引用本身),则TextBlock将不会更新。

原始问题到此为止

我有一个DAL对象包含一组人。DAL对象定义了人员之间关系的有向树图,我想显示单个人员的"儿童"相关人员的ListBox

由于关系是在DAL中定义的,而不是在人对象中,我需要定义一个知道使用适当DAL方法的应用逻辑。因此,我使用了ListBox.ItemsSource绑定中使用的转换器,如下所示(很抱歉使用了样式背后的代码:)。

  BindingBase relationsBinding = new MultiBinding()
  {
    Bindings =
    {
      new Binding() { Source = this, Path = new PropertyPath("Person") },
      new Binding() { Source = this, Path = new PropertyPath("DAL") }
    },
    Converter = PersonToRelationsConverter.Instance,
  };
  ListBox relationsList = new ListBox()
  {
    ItemTemplate = relationsBinding 
  };
  relationsList.SetBinding(ListBox.ItemsSourceProperty, relationsBinding);

转换器代码类似于:

public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
  IPerson person = values[0] as IPerson;
  IDAL<IPerson> dal = values[1] as IDAL<IPerson>;
  IPersonRelations<IPerson> relations = dal.GetPersonRelations(person);
  IEnumerable<IPerson> children = relations.OutRelated;
  return children;
}

然后我可以使用ItemTemplate来定义如何显示每个子人物。

它可能会工作,但问题是IPersonRelations<IPerson>是一个INotifyPropertyChanged对象,我想享受它的数据更改通知。以上述方式,我无法享受它,因为它只是ValueConverter的局部变量,不会返回到WPF机制。

我猜ListBox.ItemsSource只是IValueConverter必须返回某个特定对象的情况的示例,该对象不是INotifyPropertyChangedDependencyObject(或者不是我希望绑定侦听的唯一对象)。我希望有一些CompositeBinding类可以"串联"ValueConverter,这样绑定机制就会监听串联"路径"中的任何值。

我是不是错过了一些做这种事的简单方法?

谢谢你的帮助。

在单个绑定中使用多个IValueConverter来享受数据更改通知

我找到的唯一方法是实现一个绑定到中间值的"中间"依赖对象。

因此,我定义了一个中间对象BindingMiddle,用于存储查询结果,然后才绑定到结果的ShortName属性,如下所示:

<Window.Resources>
  <local:PersonQueryCreator x:Key="QueryCreator" />
  <local:BindingMiddle x:Key="QueriedPersonContainer">
   <local:BindingMiddle.MiddleValue>
    <Binding Source="{StaticResource QueryCreator}" Path="Query">
      <Binding.Converter>
        <infra:PersonSearcherConverter />
      </Binding.Converter>
    </Binding>
   </local:BindingMiddle.MiddleValue>
  </local:BindingMiddle>
</Window.Resources>
...
    <TextBox Text="{Binding Source={StaticResource QueryCreator}, Path=ShortNameToSearch}"/>
    <TextBlock Text="{Binding Source={StaticResource QueriedPersonContainer}, Path=MiddleValue.ShortName}" />

ValueConverterMiddleBinding的代码很琐碎:

public class BindingMiddle : DependencyObject
{
  #region Static Data Members
  public static DependencyProperty MiddleValueProperty = DependencyProperty.Register("MiddleValue", typeof(object), typeof(BindingMiddle));
  #endregion
  #region Public Properties
  public object MiddleValue
  {
    get { return GetValue(MiddleValueProperty); }
    set { SetValue(MiddleValueProperty, value); }
  }
  #endregion
}
public class PersonQueryCreator : DependencyObject
{
  #region Static Data Members
  public static DependencyProperty ShortNameToSearchProperty = DependencyProperty.Register("ShortNameToSearch", typeof(string), typeof(PersonQueryCreator), new PropertyMetadata(ShortNameChanged));
  private static DependencyPropertyKey QueryPropertyKey = DependencyProperty.RegisterReadOnly("Query", typeof(string), typeof(PersonQueryCreator), new PropertyMetadata());
  public static DependencyProperty QueryProperty = QueryPropertyKey.DependencyProperty;
  #endregion
  #region Public Properties
  public string ShortNameToSearch
  {
    get { return (string)GetValue(ShortNameToSearchProperty); }
    set { SetValue(ShortNameToSearchProperty, value); }
  }
  public object Query
  {
    get { return (string)GetValue(QueryProperty); }
  }
  #endregion
  #region Public Methods
  public static IPerson SearchForPerson(object query)
  {
    string shortNameToSearch = query as string;
    return new Prayer(shortNameToSearch, shortNameToSearch);
  }
  #endregion
  #region Private Methods
  private static void ShortNameChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  {
    ((PersonQueryCreator)o).SetValue(PersonQueryCreator.QueryPropertyKey, e.NewValue);
  }
  #endregion
}