我从反应4.5迁移到6.5.0,我遇到了一些问题。我有一个WPF应用程序,其按钮绑定到ReactiveCommand。以前我使用的是像这样的ReactiveCommand构造函数:
_runProcessCommand = new ReactiveCommand(CanRunProcess(null));
_runProcessCommand.Subscribe(RunImpl);
public IObservable<bool> CanRunProcess(object arg)
{
return this.WhenAny( ... )
}
现在我将其改为:
_runProcessCommand = ReactiveCommand.Create(CanRunProcess(null));
_runProcessCommand..Subscribe(RunImpl);
所以我预计行为应该完全相同但不是。我的按钮被禁用,直到我从WhenAny in CanRunProcess
绑定了一些基本上属于UI的属性。它发生在项目的许多地方,所以没有错。这两种创建ReactiveCommand的方式有什么不同吗?如何实现相同的结果?有趣的是,当我订阅CanExecuteObservable时,它按预期工作:
_runProcessCommand.CanExecuteObservable.Subscribe(x =>
{
Debug.WriteLine(x);
});
当我明确调用CanExecute时它是一样的:
var c = _runProcessCommand.CanExecute(null);
我想这可能与某些地方的懒惰有关,但我不明白为什么会这样,因为按钮应该调用CanExecute来获取当前的初始值。
当我订阅CanRunProcess时,我得到了很多错误,然后是很多真相,最后一个值是真的,我怀疑应该启用命令。
CanRunProcess(null).Subscribe(x =>
{
Debug.WriteLine(x);
});
编辑:
我已经下载了ReactiveUI来源,并且我注意到没有订阅canExecute但是正在使用Do
函数:
this.canExecute = canExecute.CombineLatest(isExecuting.StartWith(false), (ce, ie) => ce && !ie)
.Catch<bool, Exception>(ex => {
exceptions.OnNext(ex);
return Observable.Return(false);
})
.Do(x => {
var fireCanExecuteChanged = (canExecuteLatest != x);
canExecuteLatest = x;
if (fireCanExecuteChanged) {
this.raiseCanExecuteChanged(EventArgs.Empty);
}
})
.Publish();
看起来需要实例化某些东西 - 需要调用
CanExecuteObservable
或CanExecute
来实例化canExecute对象。将它绑定到按钮时为什么不创建它?
调试ReactiveUI源后,我确切知道会发生什么。 Do
是惰性函数,所以在调用connect
函数之前,处理程序不会被执行。这意味着当命令绑定到按钮并且调用canExecuteLatest
函数时CanExecute
将为false,因此按钮保持禁用状态。
可重现的示例(请注意,当我使用WhenAny执行相同的示例时,它会起作用):
public class MainViewModel : ReactiveObject
{
private ReactiveCommand<object> _saveCommand;
private string _testProperty;
private ReactiveList<string> _ReactiveList;
public ReactiveCommand<object> SaveCommand
{
get
{
return _saveCommand;
}
set { this.RaiseAndSetIfChanged(ref _saveCommand, value); }
}
public ReactiveList<string> ReactiveList
{
get
{
return _ReactiveList;
}
set { this.RaiseAndSetIfChanged(ref _ReactiveList, value); }
}
public MainViewModel()
{
ReactiveList = new ReactiveList<string>();
ReactiveList.ChangeTrackingEnabled = true;
SaveCommand = ReactiveCommand.Create(CanRunSave(null));
SaveCommand.Subscribe(Hello);
// SaveCommand.CanExecute(null); adding this line will invoke connect so the next line will run CanSave and enable the button.
ReactiveList.Add("sad");
}
public void Hello(object obj)
{
}
private IObservable<bool> CanRunSave(object arg)
{
return ReactiveList.Changed.Select(x => CanSave());
}
private bool CanSave()
{
return ReactiveList.Any();
}
}
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="test" Command="{Binding SaveCommand}" />
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
即使我向ReactiveList添加内容,按钮仍然处于禁用状态。 问题是创建命令并将其绑定到按钮之间的更新被忽略,因为没有调用连接,因此根本不会反映更改。
答案 0 :(得分:1)
您的示例中的问题是Changed
上的ReactiveList<T>
事件基本上是hot observable。即使没有订阅观察者,它也会产生变化。当观察者 订阅时,任何先前的更改都将被遗漏。
结果是CanRunSave
的订阅者不获得任何初始值。收到的第一个值是订阅后第一次更改ReactiveList
(例如下次添加/删除)的结果。
由于ReactiveCommand
中的懒惰,在 CanExecute
之前调用列表的任何更改(即可订阅observable时)都将被遗漏。在订阅时,将没有初始值,因此在更改列表之前,命令的'可执行'状态将是false
的默认值。
修复非常简单 - 确保订阅时有初始值。您可以使用StartWith
:
private IObservable<bool> CanRunSave(object arg)
{
return ReactiveList.Changed.Select(_ => Unit.Default)
.StartWith(Unit.Default)
.Select(_ => CanSave());
}