视图模型使用模型中的后台工作线程异步加载数据。模型和视图模型中的所有属性都会引发属性更改事件,并且视图中的所有属性都会按预期更新,除了 2 个按钮,其 IsEnabled
状态取决于结果加载的一些属性。
令人沮丧的是,一旦我关注视图的任何部分,或者在更新属性后设置断点(创建延迟),按钮 IsEnabled
状态就会按预期更新。所以这似乎是一个时间问题。
关于如何解决这个问题的最佳方法有任何线索吗?我正在使用 mvvm-light 框架,但这应该不重要。
我尝试将 IsEnabled 绑定到按钮,而不是仅仅依赖 Command 属性,但这没有什么区别。我已通过日志记录确认视图模型属性已设置,并且为与按钮关联的属性引发了 PropertyChanged
事件。
考虑使用 mvvm-light Messenger 从视图模型向异步完成事件的视图发送消息,然后以某种方式发送消息?触发视图刷新,但这看起来像是一个拼凑。
更新
感谢blindmeis的回答,我在没有命令绑定集的情况下测试了按钮行为,即仅绑定IsEnabled属性,并且它按预期工作!
<Button
Grid.Column="2" Content="{Binding LoadProjectsLabel}"
VerticalAlignment="Top" HorizontalAlignment="Right"
IsEnabled="{Binding CanLoadProjects}" />
显然这不太好,因为我无法再执行该命令:)但是一旦我重新添加该命令,它就会停止运行:
<Button
Grid.Column="2" Content="{Binding LoadProjectsLabel}"
VerticalAlignment="Top" HorizontalAlignment="Right"
Command="{Binding LoadProjectsCommand}" />
留下 IsEnabled
绑定并不能解决问题,但这似乎是好线索。
视图模型命令代码:
public ICommand LoadProjectsCommand
{
get
{
if (_loadProjectsCommand == null)
{
_loadProjectsCommand = new RelayCommand(loadProjects, () => CanLoadProjects);
}
return _loadProjectsCommand;
}
}
解决方法
连接 Click
事件并避免 Command
。从视图模型中解决它会很好,但这可行:
<Button
Grid.Column="2" Content="{Binding LoadProjectsLabel}"
VerticalAlignment="Top" HorizontalAlignment="Right"
IsEnabled="{Binding CanLoadProjects}"
Click="loadProjects_Click"/>
代码隐藏:
void loadProjects_Click(object sender, RoutedEventArgs e)
{
SettingsViewModel vm = (SettingsViewModel)DataContext;
vm.LoadProjectsCommand.Execute(null);
}
View model is loading data asynchronously using background worker thread in model. All properties in the model and view model raise the property changed events, and all properties are being updated in the view as expected, except 2 buttons whose IsEnabled
state depends on the outcome of some properties that are loaded.
The frustrating part is that as soon as I focus on any part of the view, or set a breakpoint after the properties are updated (create a delay), then the buttons IsEnabled
state is updated as expected. So it appears to be a timing issue.
Any clues as to how to the best way to solve this? I'm using mvvm-light framework, but that shouldn't matter.
I've tried binding IsEnabled
to the button instead of just relying on the Command
property, but that made no difference. I've confirmed via logging that view model properties are set and the PropertyChanged
event is being raised for the properties associated with the buttons.
Considering sending a message using mvvm-light messenger from the view model to the view on the async completed event and then somehow? triggering a view refresh, but that seems like a kludge.
Update
Thanks to blindmeis' answer, I tested the button behaviour without the Command binding set, i.e. just binding IsEnabled property, and it works as expected!
<Button
Grid.Column="2" Content="{Binding LoadProjectsLabel}"
VerticalAlignment="Top" HorizontalAlignment="Right"
IsEnabled="{Binding CanLoadProjects}" />
Obviously it's not great because I can no longer execute the command :) but as soon as I add the command back, it stops behaving:
<Button
Grid.Column="2" Content="{Binding LoadProjectsLabel}"
VerticalAlignment="Top" HorizontalAlignment="Right"
Command="{Binding LoadProjectsCommand}" />
Leaving IsEnabled
binding doesn't solve the problem, but that seems like a good clue.
The view model command code:
public ICommand LoadProjectsCommand
{
get
{
if (_loadProjectsCommand == null)
{
_loadProjectsCommand = new RelayCommand(loadProjects, () => CanLoadProjects);
}
return _loadProjectsCommand;
}
}
Workaround
Wire up the Click
event and avoid Command
. Would be nice to solve it from the view model, but this works:
<Button
Grid.Column="2" Content="{Binding LoadProjectsLabel}"
VerticalAlignment="Top" HorizontalAlignment="Right"
IsEnabled="{Binding CanLoadProjects}"
Click="loadProjects_Click"/>
Code behind:
void loadProjects_Click(object sender, RoutedEventArgs e)
{
SettingsViewModel vm = (SettingsViewModel)DataContext;
vm.LoadProjectsCommand.Execute(null);
}
发布评论
评论(2)
其他线程的回答:
编辑:
我使用一个更简单但不太漂亮的解决方法。我只需在“BackgroundWorker 已完成事件”中调用 OnPropertyChanged("MyICommand") 来获取命令。
编辑:
这里是另一个不错的解决方案。
Answer from other thread:
EDIT:
i use a simpler but not so beautiful workaround. i simply call OnPropertyChanged("MyICommand") for my commands in my BackgroundWorker Completed Event.
EDIT:
here is another nice solution.
您应该将命令参数属性绑定到视图模型上的任何可更新属性,并且可以执行必须使用此命令参数来启用按钮。如果命令参数的目标更新,绑定将根据 canexecute 的返回值启用/禁用。
You should bind command parameter property to any updatable property on viewmodel and can execute must use this command parameter to enable button. If command parameter's target is updated, binding will enable/disable based on return value of can excute.