WPF ProgressBar未更新

时间:2018-05-01 05:49:50

标签: wpf user-interface events mvvm progress-bar

这是随意和原型代码,因此我尝试了我认为应该工作的东西,如果它没有,则在谷歌上搜索,然后在仔细阅读类似的问题之后在这里询问。

我的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绑定到StatusMessage更新并正确显示,但是{{绑定到ProgressBar的1}}不会,并且仍为空白。如果我在事件处理程序中放置一个断点,我可以看到StatusProgress属性正在以0到100的各种值正确更新,但这并不反映在StatusProgress上。

事件处理程序在另一个线程上执行的想法(通常会导致UI更新问题)发生在我身上,但为什么一个UI元素正确更新而另一个没有?

注意:我一直纪念性愚蠢而没有静态测试ProgressBar,即只是将viewmodel&#39; s ProgressBar设置为一个值并显示shell窗口,而不通过下载循环。如果我这样做,进度条会显示一个或多或少与其StatusProgress属性对应的长度。在评论或答案中提出的布局更改建议都不会改变这一点。静态地,它始终可见并始终显示一个值。

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

以下是`MainWindow.xaml:

中的Value
StatusBar

使用<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); } } } 完成循环,然后突然显示进度条显示完整进度,即完成。

6 个答案:

答案 0 :(得分:1)

发生了什么

任何与UI无关的事情都应该在任务中完成,因为如果没有,你就会阻止UI线程和UI。 在您的情况下,下载发生在您的UI线程上,后者在更新您的UI之前等待下载完成。

解决方案

您需要执行两件事来解决问题:

  1. 从UI线程中删除工作。

  2. 确保作品可以与您的UI线程进行通信。

  3. 首先,首先以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";  });
    }
    

    然后你有下载方法:

    DelegateCommand

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

    然而,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); } 助手类:

    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 {}
    

    说明

    为了实现MVVM模式,我有了这个代码:

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

    和这个观点:

    public class MainWindowViewModel: INotifyPropertyChanged, IViewModel
    

    和这个ViewModel:

    @Slf4j
    @ControllerAdvice
    public class InternalExceptionHandler extends ResponseEntityExceptionHandler {
    
        @Override
        public ResponseEntity<Object> handleMissingServletRequestParameter(
                MissingServletRequestParameterException e,
                HttpHeaders headers,
                HttpStatus status,
                WebRequest request) {
    
            LogError error = new LogError("MissingServletRequestParameterException", 
                    HttpStatus.BAD_REQUEST,
                    String.format("Missing '%s' parameter", e.getParameterName()));
            log.debug(error.toJson());
    
            HttpErrorResponse response = new HttpErrorResponse(error.getStatus(), e.getMessage());
            return new ResponseEntity<>(response.toJson(),
                    HeaderFactory.getErrorHeaders(),
                    response.getStatus());
        }
    
        ....
    }
    

答案 1 :(得分:0)

会发生这种情况,因为StatusBarItem默认样式会将其HorizontalContentAlignment设置为Left,这导致ProgressBar水平获得少量空间。

您可以通过将ProgressBar StatusBarItem设置为StatusBarItem来完全HorizontalContentAlignment填充Stretch,也可以设置Width {1}}的{​​1}}。

答案 2 :(得分:0)

ProgressBar是DispatcherObject,DispatcherObject can be only accessed by the Dispatcher it is associated with

如果我理解你的问题你的OnFileTransferStatusChanged正在后台线程上被触发,那么既然你没有使用Dispatcher(或从UI线程)访问控件,你不能保证代码将会工作。

问题是来自非UI线程的绑定通常有效,直到它没有 - 例如在非开发机器上。

答案 3 :(得分:0)

像第一个答案一样 一定要在主UI线程上,因为OnFileTransferStatusChanged在另一个线程上。在你的活动中使用它

    Application.Current.Dispatcher.Invoke(prio, (ThreadStart)(() => 
{
   StatusMessage = fileTransferStatusEventArgs.RelativePath;
    StatusProgress = fileTransferStatusEventArgs.Progress;
}));

答案 4 :(得分:0)

我对您的示例进行了一些更改,因为您下载的文件正在处理UI线程,应用程序只是冻结您可以通过将焦点更改为其他应用程序并尝试返回来查看它 - 窗口不会显示也不会更新。

的变化:

 private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
 {
    Task.Factory.StartNew(() => ViewModel.Download());
 }

强制下载在新线程中执行。

public MainWindow()
{
    InitializeComponent();
    DataContext = ViewModel = new MainWindowViewModel();
}
public MainWindowViewModel ViewModel { get; }

删除了强制转换和访问仅限UI线程的属性DataContext。 现在我可以看到进度条filling up

答案 5 :(得分:0)

您无法看到任何更改,因为主线程AKA UI线程正忙于休眠 它没有时间更新你的用户界面 Main Thread

让任务处理您冗长的工作和更新UI的主线程

将代码包装在Task中,您可以看到进度条的进展情况。

private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
      await Task.Run(() =>
      {
           _viewModel.Download();
       });
            //_viewModel.Download(); //this will run on UI Thread

 }