ReactiveUI - 为什么这些{Binding}有效?

时间:2016-04-03 12:13:20

标签: c# wpf system.reactive reactive-programming reactiveui

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

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。

那么,发生了什么?

感谢。

1 个答案:

答案 0 :(得分:2)

一般概念

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

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

表示每当Full属性更改时,触发ParseName方法,以便更新属性_oapName。 但是,由于_oapName ObservableAsPropertyHelper 类型,这是Rx的核心,它将自行通知用户界面。

正如文档所述:

  

这将初始化'把你的'放在这里'属性(一个   ObservableAsPropertyHelper property)作为一个属性   每次更改时使用当前搜索文本长度进行更新。该   属性不能以任何其他方式设置,会引发变化   通知,因此可以在WhenAny表达式或a中使用   结合

<强>来源: 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中{li> observableToProperty(变量名称为kudos)的类型为TObj,约束为where TObj : IReactiveObject
  • name是您传递给Expression<Func<TObj, TRet>> property的媒体资源的名称,即。在您的情况下,来自name的{​​{1}}将是 NameObject

它最终会在x => x.NameObject的父ReactiveObject MainViewModel 上提升 RaisePropertyChanged

源代码ObservableAsPropertyHelper.cs @ ReactiveUI on Github