2016-04-03 63 views
1

我发布了关于codereview的问题(https://codereview.stackexchange.com/questions/124300/reactiveui-and-wpf-reusing-a-value-to-update-multiple-properties)。我在回答这个问题上的最新努力使我得到了下面的代码,这似乎工作,但我不知道什么机制实际上提供了功能!ReactiveUI - 为什么这些{绑定}工作?

MainWindow.xaml

<Window x:Class="TestHumanName.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="MainWindow" Height="392" Width="391"> 
    <StackPanel Orientation="Vertical"> 
     <StackPanel Orientation="Horizontal"> 
      <Label Content="Full" /> 
      <TextBox Width="100" Text="{Binding Full, UpdateSourceTrigger=PropertyChanged}"/> 
      <Button Content="Go"/> 
     </StackPanel> 
     <StackPanel Orientation="Horizontal"> 
      <Label Content="Title" /> 
      <TextBox Width="100" Text="{Binding NameObject.Title, Mode=OneWay}"/> 
     </StackPanel> 
     <StackPanel Orientation="Horizontal"> 
      <Label Content="First" /> 
     <TextBox Width="100" Text="{Binding NameObject.First, Mode=OneWay}"/> 
     </StackPanel> 
     <StackPanel Orientation="Horizontal"> 
      <Label Content="Middle" /> 
     <TextBox Width="100" Text="{Binding NameObject.Middle, Mode=OneWay}"/> 
     </StackPanel> 
     <StackPanel Orientation="Horizontal"> 
      <Label Content="Last" /> 
     <TextBox Width="100" Text="{Binding NameObject.Last, Mode=OneWay}"/> 
     </StackPanel> 
    </StackPanel> 
</Window> 

MainWindow.xaml.cs:

using System.Windows; 
using TestHumanName.ViewModel; 

namespace TestHumanName 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow : Window 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 
      DataContext = new MainViewModel(); 
     } 
    } 
} 

MainViewModel.cs

public class MainViewModel : ReactiveObject 
{ 
    public MainViewModel() 
    { 
     this.WhenAnyValue(x => x.Full).Where(x => x != null).Select(x => ParseName(x)) 
      .ToProperty(this, x => x.NameObject, out __oapName); 
    } 

    private string __sFull; 
    public string Full 
    { 
     get { return __sFull; } 
     set { this.RaiseAndSetIfChanged(ref __sFull, value); } 
    } 

    readonly ObservableAsPropertyHelper<Name> __oapName; 
    public Name NameObject { get { return __oapName.Value; } } 

    //NAME PARSING CODE BELOW THIS LINE 

     public class Name 
     { 
      public string Title { get; set; } 
      public string First { get; set; } 
      public string Middle { get; set; } 
      public string Last { get; set; } 
      public string Suffix { get; set; } 
     } 

     public Name ParseName(string s) 
     { 
      Name n = new Name(); 

      // Split on period, commas or spaces, but don't remove from results. 
      List<string> parts = Regex.Split(s, @"(?<=[., ])").ToList(); 

      // Remove any empty parts 
      for (int x = parts.Count - 1; x >= 0; x--) 
       if (parts[x].Trim() == "") 
        parts.RemoveAt(x); 

      if (parts.Count > 0) 
      { 
       // Might want to add more to this list 
       string[] prefixes = { "mr", "mrs", "ms", "dr", "miss", "sir", "madam", "mayor", "president" }; 

       // If first part is a prefix, set prefix and remove part 
       string normalizedPart = parts.First().Replace(".", "").Replace(",", "").Trim().ToLower(); 
       if (prefixes.Contains(normalizedPart)) 
       { 
        n.Title = parts[0].Trim(); 
        parts.RemoveAt(0); 
       } 
      } 

      if (parts.Count > 0) 
      { 
       // Might want to add more to this list, or use code/regex for roman-numeral detection 
       string[] suffixes = { "jr", "sr", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix", "x", "xi", "xii", "xiii", "xiv", "xv" }; 

       // If last part is a suffix, set suffix and remove part 
       string normalizedPart = parts.Last().Replace(".", "").Replace(",", "").Trim().ToLower(); 
       if (suffixes.Contains(normalizedPart)) 
       { 
        n.Suffix = parts.Last().Replace(",", "").Trim(); 
        parts.RemoveAt(parts.Count - 1); 
       } 
      } 

      // Done, if no more parts 
      if (parts.Count == 0) 
       return n; 

      // If only one part left... 
      if (parts.Count == 1) 
      { 
       // If no prefix, assume first name, otherwise last 
       // i.e.- "Dr Jones", "Ms Jones" -- likely to be last 
       if (n.Title == "") 
        n.First = parts.First().Replace(",", "").Trim(); 
       else 
        n.Last = parts.First().Replace(",", "").Trim(); 
      } 

      // If first part ends with a comma, assume format: 
      // Last, First [...First...] 
      else if (parts.First().EndsWith(",")) 
      { 
       n.Last = parts.First().Replace(",", "").Trim(); 
       for (int x = 1; x < parts.Count; x++) 
        n.First += parts[x].Replace(",", "").Trim() + " "; 
       n.First = n.First.Trim(); 
      } 

      // Otherwise assume format: 
      // First [...Middle...] Last 

      else 
      { 
       n.First = parts.First().Replace(",", "").Trim(); 
       n.Last = parts.Last().Replace(",", "").Trim(); 
       for (int x = 1; x < parts.Count - 1; x++) 
        n.Middle += parts[x].Replace(",", "").Trim() + " "; 
       if (n.Middle != null) n.Middle = n.Middle.Trim(); 
      } 

      return n; 
     } 
    } 
} 

我不明白的是我如何替换NameObject属性的值,并且{Binding ...}s神奇地知道他们应该更新。当然,替换NameObject属性的内容不会在它的子属性上调用OnPropertyChanged ... Name类甚至不实现INotifyPropertyChanged。

那么,这是怎么回事?

谢谢。

+0

,窗口有一个Paint事件时,窗口得到更新控制。当数据在标准Net Library类中更改时,Paint事件被调用。如果你建立你自己的类,那么你必须调用Window Paint Event。 – jdweng

+0

我看不出涂料事件与{绑定}有什么关系? –

+0

这不是不工作的绑定。这是没有发生的更新。更改绑定不会自动更改窗口中显示的内容。必须调用Paint来指示窗口更新。 – jdweng

回答

2

一般概念

我想从你的误解来自哪里。 你在这里做什么:

this.WhenAnyValue(x => x.Full).Where(x => x != null).Select(x => ParseName(x)) 
     .ToProperty(this, x => x.NameObject, out __oapName); 

意味着只要Full属性发生变化,火ParseName方法,这样它会更新属性_oapName。 但是,由于_oapNameObservableAsPropertyHelper类型,即Rx的核心,它会自行通知UI。

作为文档指出:

这会初始化“把你在这里的财产(一 ObservableAsPropertyHelper性)的将是 与当前的搜索文本长度每次变化时更新的属性。所述 属性不能以任何其他方式来设置和提高改变 通知,所以可以本身在WhenAny表达使用或结合

来源: http://docs.reactiveui.net/en/user-guide/when-any/index.html

场景

如果我们采取的ToProperty签名看看背后:

public static ObservableAsPropertyHelper<TRet> ToProperty<TObj, TRet>(
    this IObservable<TRet> This, 
    TObj source, 
    Expression<Func<TObj, TRet>> property, 
    out ObservableAsPropertyHelper<TRet> result, 
    TRet initialValue = default(TRet), 
    IScheduler scheduler = null) 
    where TObj : IReactiveObject 
    { 
     var ret = source.observableToProperty(This, property, initialValue, scheduler); 

     result = ret; 
     return ret; 
    } 

我们将看到其实际作用,正在调用扩展方法observableToProperty来生产Observ ableAsPropertyHelper。 为了更精确,里面observableToProperty我们可以看到几行是这样的:

var ret = new ObservableAsPropertyHelper<TRet>(observable, 
      _ => This.raisePropertyChanged(name), 
      _ => This.raisePropertyChanging(name), 
      initialValue, scheduler); 

,并牢记:

  • ThisobservableToProperty(变量名荣誉)是TObj类型与限制where TObj : IReactiveObject,
  • name是您已经通过Expression<Func<TObj, TRet>> property的财产的名称,即。你的情况namex => x.NameObjectNameObject

它最终会提高上的NameObjectReactiveObject这是MainViewModel RaisePropertyChanged

的源代码ObservableAsPropertyHelper.cs @ ReactiveUI on Github

+0

感谢您的回答。这是有道理的,我从引用中可以看到嵌套属性可以看到,但在引用中给出的示例中,嵌套属性明确声明如'this.WhenAnyValue(x => x.Foo.Bar.Baz); '。例如,在我的xaml中,我绑定到'NameObject.Title',这是'Name'类的实例,它不是ReactiveObject。由于我没有在代码中明确声明嵌套属性,所以我很难看到这些属性是如何通知有关更改的! –

+0

由于'WhenAny'使用'out',这意味着'ParseName'的输出被赋值为属性'_oapName'的新值(实际上是确切的参考)。你闻到属性NameObject的属性没有提示通知。整个'_oapName'引发通知,因为ObservableAsPropertyHelper封装的对象已经改变(即OAPH的属性引发了通知)。 – mwilczynski

+0

好的。凉。所以我想我现在不得不问,嵌套属性树下多少工作?如果我有'NameObject.Title.Length',这是否仍然工作? {Binding}是否足够聪明,知道'NameObject'已经改变,所以属于'NameObject'的任何嵌套属性都必须改变?我无法在文档中找到明确的答案... –