如何从不同的 ViewModel 更新文本块和进度条?
How to update a text block and progress bar from a different ViewModel?
我正在尝试了解如何从单独的 ViewModel 更新进度对话框。这是我的第一个完整的 C# 应用程序,实际上,也是我的第一个应用程序。我已经在这个特定问题上停留了几天,感觉我已经尝试从几个不同的角度来解决它,但没有成功。
让我们从一个简短的概述开始:我和我的团队将使用此应用程序来协助在现场部署 PC。它从一个非常简单的 UI 开始,登台技术人员将从列表中 select 站点 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
但是,无论我如何尝试,我都无法更新对话框。如有任何帮助,我们将不胜感激。
谢谢
万一有人需要这个,我终于弄明白了。
最初,我尝试使用 Progress()
和 IProgress<T>
,这不是您在 Caliburn Micro 中使用的方式。它是通过事件聚合完成的。我只需要了解如何以及在何处调用事件。感谢@Demon 为我指明了正确的方向。
UpdateProgressEvent
class:
public class UpdateProgressEvent
{
public UpdateProgressEvent(ProgressReportModel report)
{
_progressReport = report;
}
private ProgressReportModel _progressReport;
public ProgressReportModel ProgressReport => _progressReport;
}
其中一个大问题是我没有在正确的 ViewModel 中订阅事件。我在ProgressDialogViewModel
订阅了UpdateProgressEvent
然后在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()
只是为了测试,因为我正在评论正在完成的实际工作,但它模拟了一个较长的 运行ning 任务。
但现在它正在工作。
谢谢
我正在尝试了解如何从单独的 ViewModel 更新进度对话框。这是我的第一个完整的 C# 应用程序,实际上,也是我的第一个应用程序。我已经在这个特定问题上停留了几天,感觉我已经尝试从几个不同的角度来解决它,但没有成功。
让我们从一个简短的概述开始:我和我的团队将使用此应用程序来协助在现场部署 PC。它从一个非常简单的 UI 开始,登台技术人员将从列表中 select 站点 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
但是,无论我如何尝试,我都无法更新对话框。如有任何帮助,我们将不胜感激。
谢谢
万一有人需要这个,我终于弄明白了。
最初,我尝试使用 Progress()
和 IProgress<T>
,这不是您在 Caliburn Micro 中使用的方式。它是通过事件聚合完成的。我只需要了解如何以及在何处调用事件。感谢@Demon 为我指明了正确的方向。
UpdateProgressEvent
class:
public class UpdateProgressEvent
{
public UpdateProgressEvent(ProgressReportModel report)
{
_progressReport = report;
}
private ProgressReportModel _progressReport;
public ProgressReportModel ProgressReport => _progressReport;
}
其中一个大问题是我没有在正确的 ViewModel 中订阅事件。我在ProgressDialogViewModel
订阅了UpdateProgressEvent
然后在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()
只是为了测试,因为我正在评论正在完成的实际工作,但它模拟了一个较长的 运行ning 任务。
但现在它正在工作。
谢谢