我在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
。但是,由于_oapName
是 ObservableAsPropertyHelper 类型,这是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