迁移到 6.5.0 后禁用 ReactiveCommand
ReactiveCommand disabled after migration to 6.5.0
我正在从反应式 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
函数之前不会执行处理程序。这意味着当命令绑定到按钮和调用 CanExecute
函数时 canExecuteLatest
将为 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 添加了一些内容,按钮仍然被禁用。
问题是创建命令和将其绑定到按钮之间的更新被忽略,因为没有调用连接,所以根本不会反映更改。
您示例中的问题是 ReactiveList<T>
上的 Changed
事件本质上是 hot observable。即使没有观察者订阅,它也会产生变化。当观察者订阅时,之前的任何更改都将被遗漏。
这样做的结果是 CanRunSave
的订户将 不会 获得任何初始值。收到的第一个值将是 ReactiveList
第一次更改的结果(例如下一个 addition/removal)after 订阅。
由于 ReactiveCommand
中的惰性, 在 CanExecute
之前对列表的任何更改都会被调用(这是订阅可观察对象时) 将被错过。在订阅时,将没有初始值,因此命令的 'can execute' 状态将是默认的 false
直到列表被更改。
修复非常简单 - 确保订阅有一个初始值。您可以使用 StartWith
:
private IObservable<bool> CanRunSave(object arg)
{
return ReactiveList.Changed.Select(_ => Unit.Default)
.StartWith(Unit.Default)
.Select(_ => CanSave());
}
我正在从反应式 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
函数之前不会执行处理程序。这意味着当命令绑定到按钮和调用 CanExecute
函数时 canExecuteLatest
将为 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 添加了一些内容,按钮仍然被禁用。 问题是创建命令和将其绑定到按钮之间的更新被忽略,因为没有调用连接,所以根本不会反映更改。
您示例中的问题是 ReactiveList<T>
上的 Changed
事件本质上是 hot observable。即使没有观察者订阅,它也会产生变化。当观察者订阅时,之前的任何更改都将被遗漏。
这样做的结果是 CanRunSave
的订户将 不会 获得任何初始值。收到的第一个值将是 ReactiveList
第一次更改的结果(例如下一个 addition/removal)after 订阅。
由于 ReactiveCommand
中的惰性, 在 CanExecute
之前对列表的任何更改都会被调用(这是订阅可观察对象时) 将被错过。在订阅时,将没有初始值,因此命令的 'can execute' 状态将是默认的 false
直到列表被更改。
修复非常简单 - 确保订阅有一个初始值。您可以使用 StartWith
:
private IObservable<bool> CanRunSave(object arg)
{
return ReactiveList.Changed.Select(_ => Unit.Default)
.StartWith(Unit.Default)
.Select(_ => CanSave());
}