我正在尝试使用Caliburn.Micro.ReactiveUI编写一个非常简单的示例。此示例将Basic Configuration, Actions and Conventions sample from the Caliburn.Micro Documentation与ReactiveUI.Sample.Commands sample结合起来。我遇到的问题是,当调用任何命令时,对DisplayCommand
和StartAsyncCommand
都会抛出InvalidOperationException
,说明调用线程无法访问此对象,因为不同线程拥有它。下面我已经包含了View和ViewModel的代码,整个示例都在GitHub上,代码现在包含Paul在下面建议的修复。
搜索互联网并未提供任何信息。我想我错过了一些明显的东西,非常感谢任何帮助。
viewmodel:
public class ShellViewModel : ReactivePropertyChangedBase, IShell
{
private string personName;
public ShellViewModel(IMessageBoxService messageBoxService)
{
DisplayCommand = new ReactiveCommand(this.WhenAny(x => x.PersonName, x => !string.IsNullOrEmpty(x.Value)));
DisplayCommand.Subscribe(_ => messageBoxService.ShowMessageBox("You clicked on DisplayCommand: Name is " + PersonName));
var localProgress = new Subject<int>();
localProgress.ToProperty(this, x => x.Progress, out progress);
StartAsyncCommand = new ReactiveCommand();
StartAsyncCommand.RegisterAsyncAction(_ =>
{
var currentProgress = 0;
localProgress.OnNext(currentProgress);
while (currentProgress <= 100)
{
localProgress.OnNext(currentProgress += 10);
Thread.Sleep(100);
}
});
}
public IReactiveCommand DisplayCommand { get; protected set; }
public string PersonName
{
get { return personName; }
set
{
this.RaiseAndSetIfChanged(ref personName, value);
}
}
private ObservableAsPropertyHelper<int> progress;
public int Progress
{
get { return progress.Value; }
}
public IReactiveCommand StartAsyncCommand { get; protected set; }
}
观点:
<Window x:Class="CaliburnMicroReactiveUI.Sample.Commands.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Expander
IsExpanded="True"
Header="Simple Command">
<StackPanel>
<TextBox
Text="{Binding PersonName, UpdateSourceTrigger=PropertyChanged}" />
<Button
Command="{Binding DisplayCommand}"
Content="Display" />
</StackPanel>
</Expander>
<Expander
IsExpanded="True"
Header="Async Command">
<StackPanel>
<Button Command="{Binding StartAsyncCommand}" Content="Start" />
<ProgressBar Value="{Binding Progress, Mode=OneWay}" Height="20"/>
</StackPanel>
</Expander>
</StackPanel>
</Window>
“显示”按钮生成以下堆栈跟踪:
System.Windows.Threading.Dispatcher.VerifyAccess()
System.Windows.DependencyObject.GetValue(DependencyProperty dp)
System.Windows.Controls.Primitives.ButtonBase.get_Command()
System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()
System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object sender, EventArgs e)
System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e)
ReactiveUI.ReactiveCommand.raiseCanExecuteChanged(EventArgs e)
ReactiveUI.ReactiveCommand.<.ctor>b__5()
System.Reactive.Concurrency.Scheduler.Invoke(IScheduler scheduler, Action action)
System.Reactive.Concurrency.DefaultScheduler.<>c__DisplayClass1`1.<Schedule>b__0(Object _)
System.Reactive.Concurrency.ConcurrencyAbstractionLayerImpl.<>c__DisplayClass1.<QueueUserWorkItem>b__0(Object _)
System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
System.Threading.ThreadPoolWorkQueue.Dispatch()
System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
“开始”按钮创建此堆栈跟踪:
System.Windows.Threading.Dispatcher.VerifyAccess()
System.Windows.DependencyObject.GetValue(DependencyProperty dp)
System.Windows.Controls.Primitives.ButtonBase.get_Command()
System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()
System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object sender, EventArgs e)
System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e)
ReactiveUI.ReactiveCommand.raiseCanExecuteChanged(EventArgs e)
ReactiveUI.ReactiveCommand.<.ctor>b__5()
System.Reactive.Concurrency.Scheduler.Invoke(IScheduler scheduler, Action action)
System.Reactive.Concurrency.DefaultScheduler.<>c__DisplayClass1`1.<Schedule>b__0(Object _)
System.Reactive.Concurrency.ConcurrencyAbstractionLayerImpl.<>c__DisplayClass1.<QueueUserWorkItem>b__0(Object _)
System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
System.Threading.ThreadPoolWorkQueue.Dispatch()
System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
答案 0 :(得分:3)
我看过你的GitHub存储库,看起来你刚刚添加了Caliburn.Micro.ReactiveUI包。这意味着您只拥有ReactiveUI核心,并且您缺少特定于平台的扩展。如果你添加包reactiveui-xaml,一切都会更好(你仍然需要初始化那个messageBoxService!)。
答案 1 :(得分:0)
您的问题是您正在更改RegisterAsyncAction中的Progress
,这明确保证不会在UI线程上运行。这是一种更好的方法:
ObservableAsPropertyHelper<int> _progress;
public int Progress
{
get { return _progress.Value; }
}
public ShellViewModel(IMessageBoxService messageBoxService)
{
DisplayCommand = new ReactiveCommand(this.WhenAny(x => x.PersonName, x => !string.IsNullOrEmpty(x.Value)));
DisplayCommand.Subscribe(_ => messageBoxService.ShowMessageBox("You clicked on DisplayCommand: Name is " + PersonName));
var progress = new Subject<int>();
progress.ToProperty(this, x => x.Progress, out _progress);
StartAsyncCommand = new ReactiveCommand();
StartAsyncCommand.RegisterAsyncAction(_ =>
{
var currentProgress = 0;
progress.OnNext(currentProgress);
while (currentProgress <= 100)
{
progress.OnNext(currentProgress += 10);
Thread.Sleep(100);
}
});
}
在这种情况下,您正在使用ToProperty
保证它为您处理UI线程的编组