一段时间以来,我一直在使用ClickOnce为我的应用程序部署更新。我很高兴能够进行改进,但对当前的进度栏感到有些沮丧。有一点背景-我有一个XAML窗口类,称为“ UpdateProgress”,在对该应用程序进行更新时会打开它。这是我现在正在使用的当前代码段,它至少会通知用户正在进行进度而不会冻结应用程序/崩溃,但是不会直观地更新进度栏:
case UpdateStatuses.UpdateAvailable:
DialogResult dialogResult = System.Windows.Forms.MessageBox.Show("An update is available. Would you like to update the application now?", "Update available", MessageBoxButtons.OKCancel);
if (dialogResult.ToString() == "OK")
{
BackgroundWorker bgUpdate = new BackgroundWorker();
UpdateProgress updateNotify = new UpdateProgress();
bgUpdate.WorkerReportsProgress = true;
bgUpdate.DoWork += (uptSender, uptE) => { UpdateApplication();};
bgUpdate.ProgressChanged += (progSender, progE) => { updateNotify.updateProgress.Value = progE.ProgressPercentage; };
bgUpdate.RunWorkerCompleted += (comSender, comE) => {
updateNotify.Close();
applicationUpdated();
};
updateNotify.Show();
bgUpdate.RunWorkerAsync();
}
break;
基本上,我在上面创建了一个后台工人,该工人运行以下代码:
private static void UpdateApplication()
{
try
{
ApplicationDeployment updateCheck = ApplicationDeployment.CurrentDeployment;
//BackgroundWorker bgWorker = new BackgroundWorker();
//UpdateProgress updateNotify = new UpdateProgress();
//updateCheck.UpdateProgressChanged += (s, e) =>
//{
// updateNotify.updateProgress.Value = e.ProgressPercentage;
//};
//bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(UpdateComponent.noteUpdates);
//bgWorker.RunWorkerAsync();
//updateCheck.UpdateCompleted += (s, e) =>
//{
// updateNotify.Close();
// applicationUpdated();
//};
//updateNotify.Dispatcher.InvokeAsync(() =>
// {
//updateNotify.Show();
updateCheck.Update();
//});
//return null;
}
catch (DeploymentDownloadException dde)
{
System.Windows.MessageBox.Show("Cannot install the latest version of the application. Please check your network connection, or try again later. Error: " + dde);
//return null;
}
}
快速说明,当前,我仅创建一个名为“ updateCheck”的“ ApplicationDeployment”实例,并让它在此线程中运行更新。我之前尝试过的工作是在下面加载一些注释的代码,只是为了查看更新时应用程序崩溃。事实证明,使用我的应用程序的PROD实例进行调试时,是由于以下错误:
由于另一个线程拥有该对象,因此调用线程无法访问该对象。
现在,进行一些挖掘,我已经看到了很多有关此的不错的读物。据我了解,部分问题是我试图从与MainWindow和其他UI类分开的静态类中运行此代码。我这样做是为了保持代码的清洁和模块化,但是显然,这是有代价的。我意识到,如果进度条位于进度条窗口的代码后面,则可以绑定进度条的进度百分比,但是如果我试图坚持在我所说的静态类中运行该进度条怎么办?我曾尝试使用Dispatcher方法/ BeginInvoke()之类的方法,但不幸的是最终得到了相同的结果。
有人可以给我关于如何用ApplicationDeployment实例的更新例程的进度百分比更新窗口中进度条进度的最佳建议吗?
提前感谢一吨。
答案 0 :(得分:1)
您误解了错误原因。任何UI控件都应从拥有它的线程中进行更新。
首先,您需要包装的只是更新进度条的代码行。
然后,您可以使用两种方式来打包呼叫,即使用IProgress<T>
或Dispatcher
。前者非常酷,因为基本上您正在调用Action<T>
和Progress<T>
可以确保在实例化的同步上下文中运行它,例如UI线程。很好,因为基本上您是直接使用WPF Dispatcher将VS抽象化的。
这里有两种截然不同的方法,首先是在调用方声明,然后被调用方调用其Report
方法,其次有效地将对UI的调用包装在被调用方中。
这就是您在bgUpdate.ProgressChanged
期间要执行的操作。
现在,如果我是您,我会放弃BackgroundWorker
来支持Task
,因为这是现在这样做的首选方法,尤其是在WPF中。
使用Task
和IProgress
的最小示例:
代码:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<Button Content="DoWork" Click="Button1_Click" />
<ProgressBar Height="20" x:Name="ProgressBar1" Maximum="1.0"/>
</StackPanel>
</Window>
代码:
using System;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private async void Button1_Click(object sender, RoutedEventArgs e)
{
var progress = new Progress<double>(s => { ProgressBar1.Value = s; });
await Task.Run(() => DoWork(progress));
}
private static async Task DoWork(IProgress<double> progress = null)
{
const int count = 100;
for (var i = 0; i < count; i++)
{
await Task.Delay(50);
progress?.Report(1.0d / (count - 1) * i);
}
}
}
}
现在,您甚至不需要编写有关WPF特定的Dispatcher
的代码,代码可以在任何地方,可以更新任何框架。
您也可以使用Task.Run
取消操作:
https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=netframework-4.7.2