WhenAnyValue 未由 RaisePropertyChanged 触发
WhenAnyValue not triggered by RaisePropertyChanged
我在我正在处理的应用程序中使用 ReactiveUI 7.4。其中一个视图是 master/detail 视图。选择新的主记录后,我想从数据库中加载详细信息。在下面的示例代码中,使用 WhenAnyValue
observable 来设置 ObservableAsPropertyHelper
的值,这按预期工作。
现在,我有一个ReactiveCommand
用来保存数据。此命令调用一个存储过程,该过程对与当前所选主项目相关的子项目进行一些处理和更新。因为相关项目可能会在数据库方面发生变化,所以我想使用我已经在 WhenAnyValue
observable 中设置的逻辑来触发重新加载相关项目。
下面是一个非常简化的示例,它重现了我正在尝试做的事情。
我尝试在保存结束时调用 RaisePropertyChanged
,但它不会触发 WhenAnyValue
可观察值。我可以把选中的item设置为null,然后重置回原来的值,但是这里感觉不对。
有没有更好的方法来处理这种情况,或者有其他方法来触发WhenAnyValue
/ObservableAsPropertyHelper
?
public class SampleViewModel : ReactiveObject
{
public SampleViewModel()
{
// log any changes (we can see the RaisePropertyChanged triggers this observable, but not the one above).
this.Changed.Skip(1).Subscribe(x => Console.WriteLine($"\tChanged = {x.PropertyName}"));
// when the selected item changes, we want to load related items from the database
_relatedItems = this.WhenAnyValue(x => x.SelectedItem)
.Where(row => row != null)
.Select(row =>
{
// get related data from the cache or database...
Console.WriteLine($"\tGetting Related Items for {row["Value"]}");
return new DataTable();
})
.ToProperty(this, x => x.RelatedItems);
Save = ReactiveCommand.Create(() =>
{
// Save the selected item. Stored procedure does some extra processing of related items
// Now, we want to trigger a reload of the related items.
this.RaisePropertyChanged(nameof(SelectedItem));
/* NOTE: The following works, but feels wrong.
var oldSelection = SelectedItem;
SelectedItem = null;
SelectedItem = oldSelection;
*/
});
}
public ReactiveCommand Save { get; }
private readonly ObservableAsPropertyHelper<DataTable> _relatedItems;
public DataTable RelatedItems => _relatedItems.Value;
private DataRow _selectedItem;
public DataRow SelectedItem
{
get { return _selectedItem; }
set { this.RaiseAndSetIfChanged(ref _selectedItem, value); }
}
}
internal class Program
{
private static void Main(string[] args)
{
using (var table = new DataTable())
{
table.Columns.Add(new DataColumn("Value", typeof(string)));
table.Rows.Add(new[] { "First Row" });
table.Rows.Add(new[] { "Second Row" });
var instance = new SampleViewModel();
Console.WriteLine("Selecting First Row");
instance.SelectedItem = table.Rows[0];
Console.WriteLine();
Console.WriteLine("Saving Data");
Observable.Start(() => { }).InvokeCommand(instance.Save);
Console.WriteLine();
Console.WriteLine("Selecting Second Row");
instance.SelectedItem = table.Rows[1];
Console.WriteLine();
}
}
}
输出
Selecting First Row
Changed = SelectedItem
Getting Related Items for First Row
Changed = RelatedItems
Saving Data
Changed = SelectedItem
// should be getting related items here...
Selecting Second Row
Changed = SelectedItem
Getting Related Items for Second Row
Changed = RelatedItems
编辑(工作代码)
给我指出了正确的方向。构造函数已更新为以下内容:
public SampleViewModel()
{
// log any changes
this.Changed.Skip(1).Subscribe(x => Console.WriteLine($"\tChanged = {x.PropertyName}"));
Save = ReactiveCommand.Create(() =>
{
// Save the selected item. Stored procedure does some extra processing of related items
});
// when the save command finishes execution, get the currently selected "master" record
var saveCommandDone = Save.IsExecuting
.Where(executing => !executing).Skip(1)
.Do(_ => Console.WriteLine("\tCommand finished execution"))
.Select(_ => SelectedItem);
// when the master record changes, or data is saved, reload the related items
_relatedItems = Observable.Merge(saveCommandDone, this.WhenAnyValue(x => x.SelectedItem))
.Where(row => row != null)
.Select(row =>
{
// get related data from the cache or database...
Console.WriteLine($"\tGetting Related Items for {row["Value"]}");
return new DataTable();
})
.ToProperty(this, x => x.RelatedItems);
}
Selecting First Row
Changed = SelectedItem
Getting Related Items for First Row
Changed = RelatedItems
Saving Data
Command finished execution
Getting Related Items for First Row
Changed = RelatedItems
Selecting Second Row
Changed = SelectedItem
Getting Related Items for Second Row
Changed = RelatedItems
另一个可能的答案:
我还发现我可以将 "load related items" 逻辑转换为 ReactiveCommand
并使用 InvokeCommand
来完成同样的事情。
var loadRelatedData = ReactiveCommand.Create<DataRow, DataTable>(row => new DataTable());
_relatedItems = loadRelatedData.ToProperty(this, x => x.RelatedItems);
// when the selected item changes, trigger the command used to load related data
this.WhenAnyValue(x => x.SelectedItem)
.Where(row => row != null)
.InvokeCommand(loadRelatedData);
Save = ReactiveCommand.Create(() =>
{
// Save the data and trigger a reload of the related data.
Observable.Return(SelectedItem).InvokeCommand(loadRelatedData);
});
您的存档 ReactiveCommand
有一个 IsExecuting IObservable
。您可以尝试 Observable.Merge
将您使用 WhenAnyValue 创建的可观察对象与命令调用结合起来。
Observable.Merge(this.Save.IsExecuting, _relatedItems).Subscribe(x=>{
//do stuff
});
这是一个相关的答案:
How do I merge several observables using WhenAny(...) in ReactiveUI?
现在想想,不知道ObservableAsPropertyHelper会不会起作用。但如果没有,您可以在使用 ToProperty(...) 转换之前将 Observable 创建为单独的项目。
我在我正在处理的应用程序中使用 ReactiveUI 7.4。其中一个视图是 master/detail 视图。选择新的主记录后,我想从数据库中加载详细信息。在下面的示例代码中,使用 WhenAnyValue
observable 来设置 ObservableAsPropertyHelper
的值,这按预期工作。
现在,我有一个ReactiveCommand
用来保存数据。此命令调用一个存储过程,该过程对与当前所选主项目相关的子项目进行一些处理和更新。因为相关项目可能会在数据库方面发生变化,所以我想使用我已经在 WhenAnyValue
observable 中设置的逻辑来触发重新加载相关项目。
下面是一个非常简化的示例,它重现了我正在尝试做的事情。
我尝试在保存结束时调用 RaisePropertyChanged
,但它不会触发 WhenAnyValue
可观察值。我可以把选中的item设置为null,然后重置回原来的值,但是这里感觉不对。
有没有更好的方法来处理这种情况,或者有其他方法来触发WhenAnyValue
/ObservableAsPropertyHelper
?
public class SampleViewModel : ReactiveObject
{
public SampleViewModel()
{
// log any changes (we can see the RaisePropertyChanged triggers this observable, but not the one above).
this.Changed.Skip(1).Subscribe(x => Console.WriteLine($"\tChanged = {x.PropertyName}"));
// when the selected item changes, we want to load related items from the database
_relatedItems = this.WhenAnyValue(x => x.SelectedItem)
.Where(row => row != null)
.Select(row =>
{
// get related data from the cache or database...
Console.WriteLine($"\tGetting Related Items for {row["Value"]}");
return new DataTable();
})
.ToProperty(this, x => x.RelatedItems);
Save = ReactiveCommand.Create(() =>
{
// Save the selected item. Stored procedure does some extra processing of related items
// Now, we want to trigger a reload of the related items.
this.RaisePropertyChanged(nameof(SelectedItem));
/* NOTE: The following works, but feels wrong.
var oldSelection = SelectedItem;
SelectedItem = null;
SelectedItem = oldSelection;
*/
});
}
public ReactiveCommand Save { get; }
private readonly ObservableAsPropertyHelper<DataTable> _relatedItems;
public DataTable RelatedItems => _relatedItems.Value;
private DataRow _selectedItem;
public DataRow SelectedItem
{
get { return _selectedItem; }
set { this.RaiseAndSetIfChanged(ref _selectedItem, value); }
}
}
internal class Program
{
private static void Main(string[] args)
{
using (var table = new DataTable())
{
table.Columns.Add(new DataColumn("Value", typeof(string)));
table.Rows.Add(new[] { "First Row" });
table.Rows.Add(new[] { "Second Row" });
var instance = new SampleViewModel();
Console.WriteLine("Selecting First Row");
instance.SelectedItem = table.Rows[0];
Console.WriteLine();
Console.WriteLine("Saving Data");
Observable.Start(() => { }).InvokeCommand(instance.Save);
Console.WriteLine();
Console.WriteLine("Selecting Second Row");
instance.SelectedItem = table.Rows[1];
Console.WriteLine();
}
}
}
输出
Selecting First Row
Changed = SelectedItem
Getting Related Items for First Row
Changed = RelatedItems
Saving Data
Changed = SelectedItem
// should be getting related items here...
Selecting Second Row
Changed = SelectedItem
Getting Related Items for Second Row
Changed = RelatedItems
编辑(工作代码)
public SampleViewModel()
{
// log any changes
this.Changed.Skip(1).Subscribe(x => Console.WriteLine($"\tChanged = {x.PropertyName}"));
Save = ReactiveCommand.Create(() =>
{
// Save the selected item. Stored procedure does some extra processing of related items
});
// when the save command finishes execution, get the currently selected "master" record
var saveCommandDone = Save.IsExecuting
.Where(executing => !executing).Skip(1)
.Do(_ => Console.WriteLine("\tCommand finished execution"))
.Select(_ => SelectedItem);
// when the master record changes, or data is saved, reload the related items
_relatedItems = Observable.Merge(saveCommandDone, this.WhenAnyValue(x => x.SelectedItem))
.Where(row => row != null)
.Select(row =>
{
// get related data from the cache or database...
Console.WriteLine($"\tGetting Related Items for {row["Value"]}");
return new DataTable();
})
.ToProperty(this, x => x.RelatedItems);
}
Selecting First Row
Changed = SelectedItem
Getting Related Items for First Row
Changed = RelatedItems
Saving Data
Command finished execution
Getting Related Items for First Row
Changed = RelatedItems
Selecting Second Row
Changed = SelectedItem
Getting Related Items for Second Row
Changed = RelatedItems
另一个可能的答案:
我还发现我可以将 "load related items" 逻辑转换为 ReactiveCommand
并使用 InvokeCommand
来完成同样的事情。
var loadRelatedData = ReactiveCommand.Create<DataRow, DataTable>(row => new DataTable());
_relatedItems = loadRelatedData.ToProperty(this, x => x.RelatedItems);
// when the selected item changes, trigger the command used to load related data
this.WhenAnyValue(x => x.SelectedItem)
.Where(row => row != null)
.InvokeCommand(loadRelatedData);
Save = ReactiveCommand.Create(() =>
{
// Save the data and trigger a reload of the related data.
Observable.Return(SelectedItem).InvokeCommand(loadRelatedData);
});
您的存档 ReactiveCommand
有一个 IsExecuting IObservable
。您可以尝试 Observable.Merge
将您使用 WhenAnyValue 创建的可观察对象与命令调用结合起来。
Observable.Merge(this.Save.IsExecuting, _relatedItems).Subscribe(x=>{
//do stuff
});
这是一个相关的答案: How do I merge several observables using WhenAny(...) in ReactiveUI?
现在想想,不知道ObservableAsPropertyHelper会不会起作用。但如果没有,您可以在使用 ToProperty(...) 转换之前将 Observable 创建为单独的项目。