我在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。
那么,发生了什么?
感谢。
答案 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 。