WPF - 通过后台工作人员查看模型更新属性,但视图在聚焦之前不会更新某些元素

发布于 2024-11-07 15:42:51 字数 1922 浏览 5 评论 0 原文

视图模型使用模型中的后台工作线程异步加载数据。模型和视图模型中的所有属性都会引发属性更改事件,并且视图中的所有属性都会按预期更新,除了 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);
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

狂之美人 2024-11-14 15:42:51

其他线程的回答:

当您的BackgroundWorker完成时,调用CommandManager.InvalidateRequerySuggested();

默认情况下,WPF 仅偶尔会请求命令。否则,在每个 ICommand 实现上不断调用“CanExecute”将会产生大量开销。调用上述方法会强制 CommandManager 立即更新。

这将强制命令适当地重新启用/禁用。

编辑:

我使用一个更简单但不太漂亮的解决方法。我只需在“BackgroundWorker 已完成事件”中调用 OnPropertyChanged("MyICommand") 来获取命令。

编辑:

这里是另一个不错的解决方案。

Answer from other thread:

When your BackgroundWorker completes, call CommandManager.InvalidateRequerySuggested();

By default, Commands are only requeried occasionally by WPF. Otherwise, there would be a huge amount of overhead in constantly calling "CanExecute" on every ICommand implementation. Calling the above method forces the CommandManager to update immediately.

This will force the Commands to re-enable/disable appropriately.

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.

佼人 2024-11-14 15:42:51

您应该将命令参数属性绑定到视图模型上的任何可更新属性,并且可以执行必须使用此命令参数来启用按钮。如果命令参数的目标更新,绑定将根据 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文