【问题标题】:ReactiveUI - why do these {Binding }s work?ReactiveUI - 为什么这些 {Binding } 有效?
【发布时间】:2016-04-03 22:12:02
【问题描述】:

我在 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。

那么,发生了什么事?

谢谢。

【问题讨论】:

  • 一个窗口有一个绘制事件来控制窗口何时更新。当标准网络库类中的数据发生更改时,将调用 Paint 事件。如果您构建自己的类,那么您必须调用 Window Paint 事件。
  • 我看不出 Paint 事件与 {Binding } 有何关系?
  • 不是绑定不起作用。这是没有发生的更新。更改绑定不会自动更改窗口中显示的内容。必须调用 Paint 来告诉窗口更新。
  • 对不起,我认为你没有理解我的问题。 Binding 正在工作。
  • 您说“{Binding ...} 神奇地知道他们应该更新”。 windows REFRESH 函数采用绑定并将数据放入窗口中。刷新是在 Paint Event 中完成的。

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


【解决方案1】:

一般概念

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

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。

如文档所述:

这会初始化 'put yours here' 属性(一个 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);

并牢记:

  • observableToProperty 中的 This(变量名的荣誉)是 TObj 类型,约束为 where TObj : IReactiveObject
  • name 是您传递给Expression&lt;Func&lt;TObj, TRet&gt;&gt; property 的属性的名称,即。在您的情况下,来自x =&gt; x.NameObjectname 将是NameObject

它最终会在 NameObject 的父级 ReactiveObject 上引发 RaisePropertyChanged,即 MainViewModel

源码ObservableAsPropertyHelper.cs @ ReactiveUI on Github

【讨论】:

  • 感谢您的回答。这是有道理的,我从参考中看到可以观察嵌套属性,但在参考中给出的示例中,嵌套属性是明确声明的,例如this.WhenAnyValue(x =&gt; x.Foo.Bar.Baz);。例如,在我的 xaml 中,我绑定到 NameObject.Title,它是 Name 类的实例化,它不是 ReactiveObject。而且由于我没有在我的代码中明确声明嵌套属性,我很难看到这些属性是如何通知更改的!
  • 由于WhenAny 使用out,这意味着ParseName 的输出被分配为属性_oapName 的新值(实际上是引用)。你闻到了财产NameObject 的属性没有发出通知。整个_oapName 引发通知,因为ObservableAsPropertyHelper 包装的对象已更改(即,OAPH 的属性已引发通知)。
  • 好的。凉爽的。所以我想我现在不得不问,嵌套属性树在多远的地方起作用?如果我有NameObject.Title.Length,那还能用吗? {Binding} 是否足够聪明,可以知道NameObject 已更改,因此属于NameObject 的任何嵌套属性都必须更改?我在文档中找不到明确的答案...
  • 据我所见,ReactiveUI 的文档还在那里,但通过阅读Github code 我可以看到它会通知对象更改本身,因此对属性的更改并不重要因为你已经交换了整个对象,即。应该交换所有属性。
  • 我猜想更多的误解可能来自ReactiveUISystem.Reactive 支持,从头开始并不那么容易理解(即暴露的 API 背后发生了什么)。
猜你喜欢
  • 1970-01-01
  • 2014-02-21
  • 2017-05-27
  • 1970-01-01
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多