WPF ProgressBar未更新
这是一段随意的原型代码,因此我尝试我认为应该有效的东西,如果不行,就在谷歌上搜索,然后在阅读了类似的问题后在这里提问 我的WPF ProgressBar未更新,wpf,user-interface,events,mvvm,progress-bar,Wpf,User Interface,Events,Mvvm,Progress Bar,这是一段随意的原型代码,因此我尝试我认为应该有效的东西,如果不行,就在谷歌上搜索,然后在阅读了类似的问题后在这里提问 我的Shell视图中有以下标记: <StatusBarItem Grid.Column="0"> <TextBlock Text="{Binding StatusMessage}" /> </StatusBarItem> <Separator Grid.Column="1" /> <StatusBarItem Grid
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>
从文件下载帮助器类定期(即每n次迭代)引发事件
现在奇怪的是,当事件处理程序更新vm属性时,在Shell
视图上,绑定到StatusMessage
的TextBlock
更新并正确显示,但是绑定到StatusProgress
的ProgressBar
没有更新并保持空白。如果在事件处理程序中放置断点,我可以看到StatusProgress
属性在0到100的不同值中正确更新,但这不会反映在ProgressBar
上
我想到在另一个线程上执行事件处理程序,这通常会导致UI更新问题,但为什么一个UI元素更新正确而另一个没有
注意:我非常愚蠢,没有静态测试ProgressBar
,也就是说,只需将viewmodel的StatusProgress
设置为一个值,然后让shell窗口显示出来,而不必经过下载循环。如果执行此操作,进度条将显示与其值
属性大致对应的长度。在评论或回答中提出的布局更改建议均不会改变这一点。静态上,它始终可见并始终显示一个值
示例:我创建了一个小示例,认为它重复了问题。在这个例子中,进度条直到等待的任务完成后才会更新,我相信我的主要问题也是如此,但下载时间很长,我没有等到它完成后才注意到进度条没有更新
以下是`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>
以及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();
}
最后是文件下载程序的代码:
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);
}
}
}
公共类ProgressChangedEventArgs
{
公共int进程{get;set;}
}
公共类文件下载程序
{
公共事件处理程序已更改;
公开下载
{
对于(变量i=0;i<100;i++)
{
ProgressChanged?.Invoke(这是新的ProgressChangedEventArgs{Progress=i});
睡眠(200);
}
}
}
在本例中,进度条保持空白,直到FileDownloader
完成其循环,然后进度条突然显示完全进度,即完成。这种情况发生,因为StatusBarItem
默认样式将其HorizontalContentAlignment
设置为Left
,这将导致ProgressBar
在水平方向上仅获得少量空间
通过将StatusBarItem
的HorizontalContentAlignment
设置为Stretch
,您可以使ProgressBar
完全填充StatusBarItem
,也可以将ProgressBar
的宽度设置为DispatcherObject,并且
如果我能很好地理解您的问题,那么您的onfiletTransferStatusChanged将在后台线程上触发,因此,由于您没有使用Dispatcher(或从UI线程)访问控件,因此无法保证代码能够正常工作
问题在于,非UI线程的绑定通常会一直工作到它不工作为止——例如,在非开发机器上。就像第一个答案一样
请确保位于主UI线程上,因为OnFileTransferStatusChanged位于另一个线程上。在你的活动中使用这个
Application.Current.Dispatcher.Invoke(prio, (ThreadStart)(() =>
{
StatusMessage = fileTransferStatusEventArgs.RelativePath;
StatusProgress = fileTransferStatusEventArgs.Progress;
}));
我对您的示例做了一些更改,因为您下载的文件正在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
。
现在我可以看到进度条了。您看不到任何更改,因为您的主线程AKAUI线程正在忙于睡眠
而且它没有时间更新您的UI
让任务处理冗长的作业和更新UI的主线程
将代码包装到任务中,您就可以看到进度条的进度
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
await Task.Run(() =>
{
_viewModel.Download();
});
//_viewModel.Download(); //this will run on UI Thread
}
发生了什么事
任何与UI无关的事情都应该在任务中完成,因为如果不是这样,那么就是阻塞了UI线程和UI。
在您的情况下,下载发生在您的UI线程上,后者在更新UI之前等待下载完成
解决方案
你需要做两件事来解决你的问题:
从UI线程中删除工作
确保工作可以与您的UI线程通信
因此,首先,将下载工作作为任务开始,如下所示:
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"/>
并将按钮连接到命令,如下所示:
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"/>
调度将从非UI线程更新您的属性(在UI线程上)
然而,DelegateCommand
helper类:
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);
}
这种观点:
<Window x:Class="WpfApp1.MainWindow"
d:DataContext="{d:DesignInstance local:MainWindowViewModel,
IsDesignTimeCreatable=True}"
xmlns:local="clr-namespace:WpfApp1"
如果将HorizontalContentAlignment=“Stretch”
设置为包含ProgressBar
的StatusBarItem
,它看起来有什么不同吗?没有Width
。使用Width
。由于DataIndengine
可以很好地处理多线程,我认为很像发生了一个更简单的问题。也许你应该1。确认ifProgressBar.Value
realy未通过ProgressBar.ValueChanged
事件更改。2.如果结果为1。至少两次为正,然后确认fileTransferStatusEventArgs.Progress
每次都发生了更改,并且fileTransferStatusEventArgs.Progress
具有您的表达式的值。3.如果结果为1。是阴性
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"
public class MainWindowViewModel: INotifyPropertyChanged, IViewModel