反应式UI - 为什么这些{绑定}s工作



我在codereview(https://codereview.stackexchange.com/questions/124300/reactiveui-and-wpf-reusing-a-value-to-update-multiple-properties(上发布了一个问题。我最近在回答这个问题时所做的努力使我找到了以下代码,它似乎有效,但我不确定实际提供该功能的机制是什么!

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();
        }
    }
}

主视图模型.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。

那么,这是怎么回事呢?

谢谢。

一般概念

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

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生成 ObservableAsPropertyHelper。更准确地说,在observableToProperty里面我们可以看到像这样的几行:

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

并记住:

  • This 中的 observableToProperty 是具有 where TObj : IReactiveObject 约束的 TObj 类型
  • name 是您传递给Expression<Func<TObj, TRet>> property的属性的名称,即在您的情况下,x => x.NameObject name将是 NameObject

它最终将在NameObject的父ReactiveObject上提高RaisePropertyChanged,这是MainViewModel

源代码:ObservableAsPropertyHelper.cs @ Github 上的 ReactiveUI

相关内容

  • 没有找到相关文章

最新更新