我一直在使用ClickOnce为我的应用程序部署更新。虽然我很高兴能够进行改进,但我对目前的进度条感到有点沮丧。一点背景 - 我有一个名为“UpdateProgress”的XAML窗口类,我在为应用程序进行更新时打开它。这是我现在正在使用的当前代码片段,它至少会通知用户正在进行的操作而不会冻结应用程序/崩溃,但是不能直观地更新进度条:
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实例的更新例程的百分比进度来更新我的进度条的进度的最佳建议吗?
提前感谢超级吨。
你错误地理解了错误的原因。应该从拥有它的线程更新任何UI控件。
首先,您需要包装的只是更新进度条的代码行。
然后你有两种方法可以使用IProgress<T>
或Dispatcher
来调整你的调用。前者非常酷,因为基本上你正在调用Action<T>
和Progress<T>
确保在它被实例化的同步上下文中运行它,例如UI线程。这很好,因为基本上你是使用WPF Dispatcher直接抽象VS。
这里有两个非常不同的方法,首先在调用者处声明然后callee调用它的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