ReactiveUI - 为什么这些 {Binding } 有效?
ReactiveUI - why do these {Binding }s work?
我在 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
class 甚至没有实现 INotifyPropertyChanged。
所以,这是怎么回事?
谢谢。
一般概念
我想你的误解从何而来。
你在这里做什么:
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
的核心,它会自己通知 UI。
如文档所述:
This initialises the 'put yours here' property (an
ObservableAsPropertyHelper property) as a property that will be
updated with the current search text length every time it changes. The
property cannot be set in any other manner and raises change
notifications, so can itself be used in a WhenAny expression or a
binding.
来源:
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
中属于 TObj
类型,具有 where TObj : IReactiveObject
、 的约束
name
是您传递给 Expression<Func<TObj, TRet>> property
的 属性 的名称,即。在您的情况下,来自 x => x.NameObject
的 name
将是 NameObject
它最终会在 NameObject
的父级 ReactiveObject
上引发 RaisePropertyChanged,即 MainViewModel。
我在 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
class 甚至没有实现 INotifyPropertyChanged。
所以,这是怎么回事?
谢谢。
一般概念
我想你的误解从何而来。 你在这里做什么:
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
的核心,它会自己通知 UI。
如文档所述:
This initialises the 'put yours here' property (an ObservableAsPropertyHelper property) as a property that will be updated with the current search text length every time it changes. The property cannot be set in any other manner and raises change notifications, so can itself be used in a WhenAny expression or a binding.
来源: 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
中属于TObj
类型,具有where TObj : IReactiveObject
、 的约束
name
是您传递给Expression<Func<TObj, TRet>> property
的 属性 的名称,即。在您的情况下,来自x => x.NameObject
的name
将是 NameObject
它最终会在 NameObject
的父级 ReactiveObject
上引发 RaisePropertyChanged,即 MainViewModel。