gpt4 book ai didi

WPF ProgressBar 不更新

转载 作者:行者123 更新时间:2023-12-04 14:11:30 29 4
gpt4 key购买 nike

这是随意的原型(prototype)代码,因此我尝试了我认为应该有效的方法,如果没有,则在谷歌上搜索,然后在仔细阅读类似问题后在这里提问。

我的 Shell 中有以下标记看法:

<StatusBarItem Grid.Column="0">
<TextBlock Text="{Binding StatusMessage}" />
</StatusBarItem>
<Separator Grid.Column="1" />
<StatusBarItem Grid.Column="2">
<ProgressBar Value="{Binding StatusProgress}" Minimum="0" Maximum="100" Height="16" Width="198" />
</StatusBarItem>

然后在 ShellViewModel我有以下两个属性和一个事件处理程序:
private string _statusMessage;
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
private double _statusProgress;
public double StatusProgress
{
get => _statusProgress;
set => SetProperty(ref _statusProgress, value);
}

private void OnFileTransferStatusChanged(object sender, FileTransferStatusEventArgs fileTransferStatusEventArgs)
{
StatusMessage = fileTransferStatusEventArgs.RelativePath;
StatusProgress = fileTransferStatusEventArgs.Progress;
}

该事件从文件下载帮助程序类定期引发,即每 n 次迭代。

现在奇怪的是,当事件处理程序更新 vm 属性时, Shell看, TextBlock绑定(bind)到 StatusMessage更新和显示正确,但 ProgressBar绑定(bind)到 StatusProgress没有,并且保持空白。如果我在事件处理程序中设置一个断点,我可以看到 StatusProgress属性被正确更新为从 0 到 100 的各种值,但这并未反射(reflect)在 ProgressBar 上.

我想到了在另一个线程上执行事件处理程序的想法,这通常会导致 UI 更新问题,但是为什么一个 UI 元素可以正确更新而另一个没有呢?

注意:我一直非常愚蠢,没有测试 ProgressBar静态的,即只设置 View 模型的 StatusProgress到一个值并获取要显示的 shell 窗口,而无需通过下载循环。如果我这样做,进度条显示的长度或多或少对应于它的 Value。属性(property)。评论或答案中提出的布局更改建议都不会改变这一点。静态地它总是可见的并且总是显示一个值。

示例:我创建了一个相信重复问题的小例子。在示例中,进度条在等待的任务完成之前不会更新,我相信我的主要问题就是这种情况,但下载时间很长,我没有等到它完成才注意到进度条没有更新。

这里是 StatusBar在`MainWindow.xaml:
<StatusBar DockPanel.Dock="Bottom" Height="20">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem Grid.Column="2">
<ProgressBar Value="{Binding StatusProgress}" Maximum="100" Minimum="0" Height="16" Width="198" />
</StatusBarItem>
</StatusBar>

使用 MainWindow.xaml.cs 中的代码:
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
public MainWindowViewModel ViewModel => (MainWindowViewModel)DataContext;
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
ViewModel.Download();
}

以及 MainWindowViewModel 中的代码:
private string _statusMessage = "Downloading something";
public string StatusMessage
{
get => _statusMessage;
set
{
if (value == _statusMessage) return;
_statusMessage = value;
OnPropertyChanged();
}
}

private int _statusProgress;
public int StatusProgress
{
get => _statusProgress;
set
{
if (value == _statusProgress) return;
_statusProgress = value;
OnPropertyChanged();
}
}

public void Download()
{
var dl = new FileDownloader();
dl.ProgressChanged += (sender, args) =>
{
StatusProgress = args.Progress;
};
dl.Download();
}

最后是 FileDownloader 的代码:
public class ProgressChangedEventArgs
{
public int Progress { get; set; }
}

public class FileDownloader
{
public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
public void Download()
{
for (var i = 0; i < 100; i++)
{
ProgressChanged?.Invoke(this, new ProgressChangedEventArgs{Progress = i});
Thread.Sleep(200);
}
}
}

在示例中,进度条保持空白,直到 FileDownloader完成它的循环,然后进度条突然显示完整的进度,即完成。

最佳答案

发生了什么

任何与 UI 无关的事情都应该在任务中完成,因为如果不这样做,您将阻塞 UI 线程和 UI。
在您的情况下,下载发生在您的 UI 线程上,后者在更新您的 UI 之前等待下载完成。

解决方案

你需要做两件事解决您的问题:

  • 从 UI 线程中删除工作。
  • 确保工作可以与您的 UI 线程进行通信。

  • 所以,首先,以 Task 的身份开始下载工作。像这样:
    private ICommand _startDownloadCommand;
    public ICommand StartDownloadCommand
    {
    get
    {
    return _startDownloadCommand ?? (_startDownloadCommand = new DelegateCommand(
    s => { Task.Run(() => Download()); },
    s => true));
    }
    }

    并将按钮连接到命令,如下所示:
    <Button Command="{Binding StartDownloadCommand}" Content="Start download" Height="20"/>

    然后你下载方法是这样的:
    public void Download()
    {
    Application.Current.Dispatcher.Invoke(() => { StatusMessage = "download started"; });

    var dl = new FileDownloader();
    dl.ProgressChanged += (sender, args) =>
    {
    Application.Current.Dispatcher.Invoke(() => { StatusProgress = args.Progress; });
    };
    dl.Download();

    Application.Current.Dispatcher.Invoke(() => { StatusMessage = "download DONE"; });
    }

    调度将从非 UI 线程更新您的属性(在 UI 线程上)。

    然而, DelegateCommand助手类:
    public class DelegateCommand : ICommand
    {
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;
    public event EventHandler CanExecuteChanged;

    public DelegateCommand(Action<object> execute)
    : this(execute, null) {}

    public DelegateCommand(Action<object> execute,
    Predicate<object> canExecute)
    {
    _execute = execute;
    _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
    public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

    评论

    为了实现 MVVM 模式,我有以下代码:
    public partial class MainWindow : IView
    {
    public IViewModel ViewModel
    {
    get { return (IViewModel)DataContext; }
    set { DataContext = value; }
    }

    public MainWindow()
    {
    DataContext = new MainWindowViewModel();
    }
    }

    public interface IViewModel {}

    public interface IView {}

    这个观点:
    <Window x:Class="WpfApp1.MainWindow"
    d:DataContext="{d:DesignInstance local:MainWindowViewModel,
    IsDesignTimeCreatable=True}"
    xmlns:local="clr-namespace:WpfApp1"

    和这个 ViewModel:
    public class MainWindowViewModel: INotifyPropertyChanged, IViewModel

    关于WPF ProgressBar 不更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50111892/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com