首页 > 解决方案 > 如何从不同的 ViewModel 更新文本块和进度条?

问题描述

我试图弄清楚如何从单独的 ViewModel 更新进度对话框。这是我的第一个完整的 C# 应用程序,实际上,我的第一个应用程序。我已经在这个特定问题上停留了几天,并且觉得我已经尝试从几个不同的角度来处理它,但没有成功。

让我们从简要概述开始:我和我的团队将使用此应用程序来协助在现场部署 PC。它从一个非常简单的 UI 开始,登台技术将从列表中选择站点 ID,然后单击“配置”以启动该过程。该过程的其余部分将不需要任何用户交互。

我正在尝试在初始 UI 顶部显示一个进度对话框,以向技术人员提供进度指示。

目前,我可以让进度框出现,但无法更新它。

这是 ShellViewModel 的代码:

namespace StagingWpfUI.ViewModels
{
    public class ShellViewModel : Conductor<object>, IHandle<ConfigureServerEvent>, IHandle<LoadDeviceInfoEvent>
    {
        #region Private Variables

        private readonly IEventAggregator _events;
        private readonly ProgressDialogViewModel _progressVM;
        private readonly DeviceInfoViewModel _deviceInfoVM;
        private readonly IWindowManager _window;

        #endregion

        #region Constructor

        public ShellViewModel(IEventAggregator events,
                              IWindowManager window,
                              ProgressDialogViewModel progressVM,
                              DeviceInfoViewModel deviceInfoVM)
        {
            _events = events;
            _progressVM = progressVM;
            _deviceInfoVM = deviceInfoVM;
            _window = window;
            _events.SubscribeOnUIThread(this);

            ActivateItemAsync(IoC.Get<DeviceConfigureViewModel>(), new CancellationToken());
        }

        #endregion

        #region Public Methods

        public async Task HandleAsync(ConfigureServerEvent message, CancellationToken cancellationToken)
        {
            //await ActivateItemAsync(_progress);
            dynamic settings = new ExpandoObject();

            settings.WindowStartupLocation = WindowStartupLocation.CenterOwner;
            settings.ResizeMode = ResizeMode.NoResize;
            settings.Title = "Progress";
            settings.WindowStyle = WindowStyle.None;

            await DeactivateItemAsync(IoC.Get<DeviceConfigureViewModel>(), true, new CancellationToken());

            await _window.ShowWindowAsync(_progressVM, null, settings);
        }

        public async Task HandleAsync(LoadDeviceInfoEvent message, CancellationToken cancellationToken)
        {
            await DeactivateItemAsync(_deviceInfoVM, true);
        }

        #endregion

DeviceConfigureViewModel(主 UI)在应用程序启动时被激活。当从 DeviceConfigureVM 调用 ConfigureServer 方法时会调用 ProgressDialogVM。这很好用。但是当我尝试做任何工作时,进度不会更新。

这是 ProgressDialogView.xaml 的代码:

<UserControl x:Class="StagingWpfUI.Views.ProgressDialogView"
        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"
        xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
        mc:Ignorable="d" Background="LightBlue"
        d:DesignHeight="200" d:DesignWidth="650">
    <Grid Margin="20">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1" >
            <TextBlock x:Name="ProgressMessage" Text="This is a test message" 
                   HorizontalAlignment="Center" Margin="0 0 0 10" Style="{StaticResource ProgressMessage}"/>
            <ProgressBar x:Name="StagingProgress" Height="25" Width="600" Value="{Binding Path=CurrentProgress}"/>
        </StackPanel>
    </Grid>
</UserControl>

和 ProgressDialogViewModel:

namespace StagingWpfUI.ViewModels
{
    public class ProgressDialogViewModel : Screen
    {
        private int _currentProgress;
        private string _progressMessage;

        public int CurrentProgress
        {
            get => _currentProgress;
            set
            {
                _currentProgress = value;
                NotifyOfPropertyChange(() => CurrentProgress);
            }
        }

        public string ProgressMessage
        {
            get => _progressMessage;
            set
            {
                _progressMessage = value;
                NotifyOfPropertyChange(() => ProgressMessage);
            }
        }
    }
}

在 ConfigureServer 方法中,我这样调用 ConfigureServerEvent:

await _events.PublishOnUIThreadAsync(new ConfigureServerEvent());

然后进入一个单独的私有方法,配置:

private void Configure(StagingModel model)
        {
            string csvPath = Path.Combine(_scriptPath, _sitesCsv);
            string outputFile = Path.Combine(_scriptPath, "staging.csv");

            _fileHelper.DeleteMarkerFile("first", "first.done");

            SiteModel siteModel = _textHelper.GetSiteModelByID(model.SiteId, csvPath);

            siteModel.StagingTech = model.StagingTech;

            //GetStagingFiles(siteModel.SiteID);

            if (model.HDReplacement == 0)
            {
                _logger.Info("Full server replacement selected...");
                FullServerReplacement(siteModel);
            }
            else
            {
                _logger.Info("Hard drive replacement selected...");

                if (model.HDLetter.ToLower() == "c:")
                {
                    _logger.Info("C: drive replacement selected...");
                    CDriveReplacement(siteModel);
                }
                else
                {
                    _logger.Info("D: drive replacement selected...");
                    DDriveReplacement(siteModel);
                }
            }

            _logger.Info($"Writing site info for site { siteModel.SiteID } to CSV file...");

            List<SiteModel> siteList = new List<SiteModel> { siteModel };
            _textHelper.WriteToCsv(siteList, outputFile, false);
        }

该方法最终实现Progress,ProgressChanged 事件称为 FullServerReplacement:

private void FullServerReplacement(SiteModel model)
        {
            Progress<ProgressReportModel> progress = new Progress<ProgressReportModel>();
            progress.ProgressChanged += Progress_ProgressChanged;

            StageMachine(model, progress);
        }

最后,在 StageMachine 方法中,我正在“尝试”报告进度:

   private void StageMachine(SiteModel site, IProgress<ProgressReportModel> progress)
    {
        ProgressReportModel report = new ProgressReportModel();

        _logger.Info("***************************Stage Machine selected.***************************");
        report.ProgressMessage = "Beginning staging...";
        report.CurrentProgress = 0;
        progress.Report(report);
        // Do more work and report the progress

但是,无论我尝试了什么,我都无法更新对话框。这里的任何帮助将不胜感激。

谢谢

标签: c#wpfmvvmcaliburn.micro

解决方案


万一有人需要这个,我终于想通了。

最初,我尝试使用Progress()and IProgress<T>,这不是您在 Caliburn Micro 中使用的方式。它是通过事件聚合完成的。我只需要弄清楚如何以及在哪里调用该事件。感谢@Demon 为我指明了正确的方向。

UpdateProgressEvent班级:

public class UpdateProgressEvent
    {
        public UpdateProgressEvent(ProgressReportModel report)
        {
            _progressReport = report;
        }

        private ProgressReportModel _progressReport;

        public ProgressReportModel ProgressReport => _progressReport;
    }

最大的问题之一是我没有在正确的 ViewModel 中订阅该事件。我订阅了UpdateProgressEvent中的ProgressDialogViewModel,然后更新了Handle方法中的进度:

 public class ProgressDialogViewModel : Screen, IHandle<UpdateProgressEvent>
    {
        public ProgressDialogViewModel(IEventAggregator events)
        {
            _events = events;
            _events.SubscribeOnUIThread(this);
        }

        private int _currentProgress;
        private string _progressMessage;
        private readonly IEventAggregator _events;

        public int CurrentProgress
        {
            get => _currentProgress;
            set
            {
                _currentProgress = value;
                NotifyOfPropertyChange(() => CurrentProgress);
            }
        }

        public string ProgressMessage
        {
            get => _progressMessage;
            set
            {
                _progressMessage = value;
                NotifyOfPropertyChange(() => ProgressMessage);
            }
        }

        public Task HandleAsync(UpdateProgressEvent message, CancellationToken cancellationToken)
        {
            UpdateProgress(message.ProgressReport.Progress, message.ProgressReport.Message);

            return Task.CompletedTask;
        }

        private void UpdateProgress(int val, string msg)
        {
            ProgressMessage = msg;
            CurrentProgress = val;
            NotifyOfPropertyChange(() => ProgressMessage);
            NotifyOfPropertyChange(() => CurrentProgress);
        }
    }

ProgressDialogView我稍微修改了一下:

 <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1" >
            <TextBlock x:Name="ProgressMessage" Text="This is a test"
                   HorizontalAlignment="Center" Margin="0 0 0 10" Style="{StaticResource ProgressMessage}"/>
            <ProgressBar x:Name="CurrentProgress" Height="25" Width="600" IsIndeterminate="False"/>
        </StackPanel>

最大的不同是我必须在单独的线程上运行该Configure方法,正如@mm8 所提到的:

public async Task ConfigureServer()
    {
        _logger.Info("Starting to configure server...");

        int hdReplacement = 0;
        if (SelectedHDReplacement.ToLower() == "yes")
        {
            hdReplacement = 1;
        }

        StagingModel staging = new StagingModel
        {
            SiteId = SelectedSiteID,
            StagingTech = TechName,
            HDReplacement = hdReplacement,
            HDLetter = string.IsNullOrWhiteSpace(SelectedHDLetter) ? null : SelectedHDLetter
        };

        await _events.PublishOnUIThreadAsync(new ConfigureServerEvent());

        await Task.Run(() => Configure(staging));
    }

然后,在StageMachine方法内部:

        public async void StageMachine(SiteModel site)
    {
        ProgressReportModel progress = new ProgressReportModel();

        _logger.Info("***************************Stage Machine selected.***************************");
        progress.Message = "Stage machine selected...";
        progress.Progress = 0;
        await _events.PublishOnUIThreadAsync(new UpdateProgressEvent(progress));
        Thread.Sleep(10000);
        //string iberdir = _serverHelper.GetEnvironmentVariable("IBERDIR");

        _logger.Info("Setting computer name...");
        progress.Message = "Setting computer name...";
        progress.Progress = 5;
        await _events.PublishOnUIThreadAsync(new UpdateProgressEvent(progress));
        Thread.Sleep(10000);
        //ServerHelper.SetComputerName(site.ServerName);
        // Do more work here.

Thread.Sleep()只是为了测试,因为我正在评论正在完成的实际工作,但它模拟了一个长期运行的任务。

但现在它正在工作。

谢谢


推荐阅读