【问题标题】:WPF Progress Bar - Update Progress from ClickOnce UpgradeWPF 进度条 - 从 ClickOnce 升级更新进度
【发布时间】:2019-04-12 01:26:37
【问题描述】:

一段时间以来,我一直在使用 ClickOnce 为我的应用程序部署更新。虽然我很高兴能够做出改进,但我对当前的进度条有点失望。一点背景知识 - 我有一个名为“UpdateProgress”的 XAML 窗口类,当应用程序进行更新时我会打开它。这是我现在正在使用的当前代码 sn-p,它至少会通知用户正在取得进展而不会冻结应用程序/崩溃,但不会直观地更新进度条:

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 实例的更新例程的百分比进度来更新我的进度条的进度给我最好的建议吗?

提前非常感谢。

【问题讨论】:

  • IProgress<T> 呢?
  • @Aybe 你的意思是,在 UpdateApplication 方法上?
  • 我想我只是没有把两个和两个放在一起。我看到他们正在任务中运行工作,这似乎是有道理的,但我没有连接任务将如何帮助将进度报告到进度条?
  • 检查我的答案,希望你现在就明白了。

标签: c# wpf progress-bar clickonce auto-update


【解决方案1】:

您对错误原因的理解有误。任何 UI 控件都应该从拥有它的线程更新。

首先,您需要包装的只是更新进度条的代码行。

那么您有两种方法来结束您的通话,使用IProgress<T>Dispatcher。前者非常酷,因为基本上你正在调用 Action<T>Progress<T> 确保在它被实例化的同步上下文中运行它,例如用户界面线程。这很好,因为基本上你是直接使用 WPF Dispatcher 抽象事物 VS。

这里有两种完全不同的方法,第一种是在调用者处声明,然后被调用者调用其Report 方法,第二种有效地将对 UI 的调用包装在被调用者中。

这就是您在bgUpdate.ProgressChanged 期间执行的需要注意的事情。

现在,如果我是你,我会放弃 BackgroundWorker 转而使用 Task,因为它是现在的首选方式,尤其是在 WPF 中。

使用TaskIProgress 的最小示例:

代码:

<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

【讨论】:

  • 我终于明白了!这太棒了。不过有一个问题,@Aybe - 一直在尝试通过继续操作查找继续任务,该操作运行另一种 Task 方法,该方法在更新完成后重新启动应用程序。这是您建议在更新完成后重新启动应用程序的方法吗?如果没有,你有什么建议?
  • 从来没有这样做过,我会尝试stackoverflow.com/questions/23405848/… 中最简单的例子(参见上面已经回答的)。现在我可能会为此同步(不继续)。希望对您有所帮助,否则请再问一个问题!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多