WPF Progress Bar - Обновление прогресса от ClickOnce Upgrade - PullRequest
0 голосов
/ 12 апреля 2019

Я уже некоторое время развертываю обновления для моего приложения с 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;
    }
}

Краткое объяснение, в настоящее время я только создаю экземпляр ApplicationDeployment под названием updateCheck и просто запускаю обновление в этой теме. То, что я пытался сделать раньше, - это загрузка некоторого прокомментированного кода ниже, только чтобы увидеть сбой приложения при обновлении. Оказывается, при отладке с экземпляром PROD моего приложения это происходит из-за следующей ошибки:

Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку.

Теперь, немного покопавшись, я видел довольно много хороших отзывов об этом. Насколько я понимаю, отчасти проблема в том, что я пытаюсь запустить этот код из статического класса, отделенного от моего MainWindow и других классов пользовательского интерфейса. Я делаю это, чтобы сохранить мой код чистым и модульным, но, очевидно, это имеет свою цену. Я понимаю, что можно связать процент прогресса индикатора выполнения, если он находится в коде позади, скажем, окна индикатора выполнения, но что, если я пытаюсь придерживаться этого в статическом классе, о котором я говорю вместо этого? Я пытался использовать такие вещи, как методы Dispatcher / BeginInvoke (), но, к сожалению, в итоге получился тот же результат.

Может ли кто-нибудь дать мне лучшее предложение о том, как обновить ход выполнения моего индикатора выполнения в окне с процентом хода выполнения процедуры обновления экземпляра ApplicationDeployment?

Заранее большое спасибо.

1 Ответ

1 голос
/ 12 апреля 2019

Вы неправильно понимаете причину своей ошибки.Любой элемент управления пользовательского интерфейса должен быть обновлен из потока, которому он принадлежит.

Сначала вам нужно обернуть только строку кода, которая обновляет ваш индикатор выполнения.

Затем у вас есть два способа переноса.ваш звонок, используя IProgress<T> или Dispatcher.Первое довольно круто, поскольку в основном вы вызываете Action<T> и Progress<T>, что гарантирует запуск его в контексте синхронизации, в котором он был создан, например, в потоке пользовательского интерфейса.Это хорошо, поскольку в основном вы абстрагируете вещи VS непосредственно с помощью WPF Dispatcher.

Здесь два действительно разных подхода: сначала вызывается в вызывающей стороне, затем вызываемый абонент вызывает его метод Report, а во-вторых, эффективно оборачивает вызов пользовательского интерфейса в вызываемую.

Это то, что вы выполняете в течение 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);
            }
        }
    }
}

enter image description here

Теперь вашему коду даже не нужно знать о Dispatcher, который специфичен для WPF, код может быть где угодно, обновить любой фреймворк.

Вы также можетеотмените операцию с помощью Task.Run:

https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=netframework-4.7.2

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...