这是随意的原型(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 之前等待下载完成。
解决方案
你需要做两件事解决您的问题:
所以,首先,以
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/