WPF、PRISM 和事件聚合器
WPF, PRISM and EventAggregrator
我在我的应用程序中使用 EventAggregator 时遇到了一些问题。我面临的问题是 UI 在当前处理停止之前不会更新。我的印象是 EventAggregator 运行 在它自己的线程中,因此应该能够在事件发布后立即更新 UI。我是不是误解了这个概念?
下面是我的代码
Bootstrapper.cs
class Bootstraper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return ServiceLocator.Current.GetInstance<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
}
App.xmal.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var bs = new Bootstraper();
bs.Run();
}
}
MainWindow.xmal
<Window x:Class="TransactionAutomationTool.Views.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"
xmlns:local="clr-namespace:TransactionAutomationTool"
xmlns:views="clr-namespace:TransactionAutomationTool.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800">
<Grid>
<views:HeaderView x:Name="HeaderViewCntl" Margin="20,21,10,0" Height="70" Width="740" HorizontalAlignment="Left" VerticalAlignment="Top" />
<views:ProcessSelectionView x:Name="ProcessSelectionViewControl" Margin="20,105,0,0" Height="144" Width="257" HorizontalAlignment="Left" VerticalAlignment="Top" />
<views:ProcessInputView x:Name="ProcessInputViewControl" Margin="20,280,0,0" Height="218" Width="257" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<views:ProcessLogView x:Name="ProcessLogViewControl" Margin="298,105,0,0" Height="445" Width="462" HorizontalAlignment="Left" VerticalAlignment="Top" />
<views:ButtonsView x:Name="ButtonViewControl" Margin="0,513,0,0" Height="37" Width="300" HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
ProcessLogView.xaml
<UserControl x:Class="TransactionAutomationTool.Views.ProcessLogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TransactionAutomationTool.Views"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="445" d:DesignWidth="462">
<UserControl.Resources>
<DataTemplate x:Key="TwoLinkMessage">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Message}" />
<TextBlock>
<Hyperlink NavigateUri="{Binding Link}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="HyperLinkClicked">
<ei:CallMethodAction MethodName="HyperLinkClicked" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock Text="{Binding Link}"/>
</Hyperlink>
</TextBlock>
<TextBlock>
<Hyperlink NavigateUri="{Binding SecondLink}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="HyperLinkClicked">
<ei:CallMethodAction MethodName="HyperLinkClicked" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock Text="{Binding SecondLink}"/>
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="LinkMessage">
<TextBlock>
<Hyperlink NavigateUri="{Binding Link}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="HyperLinkClicked">
<ei:CallMethodAction MethodName="HyperLinkClicked" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock Text="{Binding Message}"/>
</Hyperlink>
</TextBlock>
</DataTemplate>
<DataTemplate x:Key="Default">
<TextBlock Text="{Binding Message}" />
</DataTemplate>
</UserControl.Resources>
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="15">
<!--<ListBox x:Name="lbxProgress" HorizontalAlignment="Left" Height="408" Margin="5,5,0,0" VerticalAlignment="Top" Width="431" Foreground="Black" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding LogMessage}" BorderThickness="0" />-->
<ListView Name="lvProgress" ItemsSource="{Binding LogMessage}" Margin="9" BorderThickness="0">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContentTemplate" Value="{StaticResource Default}" />
<Style.Triggers>
<DataTrigger Binding="{Binding LinkNum}" Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource Default}" />
</DataTrigger>
<DataTrigger Binding="{Binding LinkNum}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource LinkMessage}" />
</DataTrigger>
<DataTrigger Binding="{Binding LinkNum}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource TwoLinkMessage}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Border>
ProcessLogViewModel.cs
class ProcessLogViewModel: EventsBase
{
private ObservableCollection<LogPayload> logMessage;
public ObservableCollection<LogPayload> LogMessage
{
get { return logMessage; }
set { SetProperty(ref logMessage, value); }
}
public ProcessLogViewModel()
{
//If statement is required for viewing the MainWindow in design mode otherwise errors are thrown
//as the ProcessLogViewModel has parameters which only resolve at runtime. I.E. events
if (!(bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue)
{
events.GetEvent<LogUpdate>().Subscribe(UpdateProgressLog);
LogMessage = new ObservableCollection<LogPayload>();
}
}
public void HyperLinkClicked(object sender, RequestNavigateEventArgs e)
{
System.Diagnostics.Process.Start(e.Uri.AbsoluteUri);
}
private void UpdateProgressLog(LogPayload msg)
{
LogMessage.Add(msg);
}
}
EventsBase.cs
public class EventsBase: BindableBase
{
public static IServiceLocator svc = ServiceLocator.Current;
public static IEventAggregator events = svc.GetInstance<IEventAggregator>();
}
LogEvents.cs
public class 日志更新:PubSubEvent { }
public class LogEvents : EventsBase
{
public static void UpdateProcessLogUI(LogPayload msg)
{
events.GetEvent<LogUpdate>().Publish(msg);
}
}
日志事件结构
public struct LogPayload
{
public string Message { get; set; }
public int LinkNum { get; set; }
public string Link { get; set; }
public string SecondLink { get; set; }
}
然后,如果我将电子表格拖放到 ProcessInputView 上,则会在我的 ProcessInputViewModel.cs
中命中以下代码
public void FileDropped(object sender, DragEventArgs e)
{
string[] files;
string[] cols;
TextBox txtFileName = (TextBox)sender;
SpreadsheetCheck result = new SpreadsheetCheck();
DDQEnums.TranTypes tranType;
List<string> fileFormats = new List<string>();
fileFormats.Add(Constants.FileFormats.XLS);
fileFormats.Add(Constants.FileFormats.XLSX);
if (e.Data.GetDataPresent(DataFormats.FileDrop, true))
{
files = e.Data.GetData(DataFormats.FileDrop, true) as string[];
if (files.GetLength(0) > 1)
{
result.IsValid = false;
result.Message = "Only drop one file per input box";
}
else
{
result = Utils.CheckIfSpreadsheetIsValidForInput(files[0], fileFormats, (DDQEnums.TranTypes)txtFileName.Tag, out tranType);
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload(string.Format("Checking {0} Spreadsheet Column Format", tranType)));
if (result.IsValid)
{
cols = Utils.GetSpreadsheetColumns(tranType);
if (cols.GetLength(0) > 0)
{
result = CheckSpreadsheetColumnFormat(files[0], cols, tranType);
txtFileName.Text = Path.GetFileName(files[0]);
}
else
{
result.IsValid = false;
result.Message = "Unable to get column definations to be used";
}
}
}
IsInputValid = result.IsValid;
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload(result.Message));
ProcessInputViewEventsPublish.SendInputValidStatus(IsInputValid, SelectedProcess, files[0]);
}
else
{
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload("Unable to get the file path for the dropped file"));
}
}
除了 ProcessLog 列表视图在 FileDropped 方法完成之前不会更新外,一切正常。通过在 LogEvents.UpdateProcessLogUI 方法之后的 FileDropped 方法中添加 thread.sleep 可以更清楚地看到这一点。
我是否错误地实施了这一点?如果是,我如何在使用 IEventAggregator 时在 ProcessLogView 列表视图中获取实时更新?
好吧,事实证明我很愚蠢。我的 ProcessInputViewModel 中的 FilesDropped 方法在 UI 线程上 运行ning,所以当然 UI 直到处理完成后才更新。
我通过创建一个新方法 FileDroppedBackground 并 运行在一个新线程上解决这个问题。
FileDropped 方法
public void FileDropped(object sender, DragEventArgs e)
{
TextBox txtFileName = (TextBox)sender;
DDQEnums.TranTypes tag = (DDQEnums.TranTypes)txtFileName.Tag;
string fileName = string.Empty;
new Thread(() => fileName = FileDroppedBackground(tag, e)).Start();
txtFileName.Text = fileName;
}
FileDroppedBackground 方法
private string FileDroppedBackground(DDQEnums.TranTypes tag, DragEventArgs e)
{
string[] files;
string[] cols;
string returnValue = string.Empty;
SpreadsheetCheck result = new SpreadsheetCheck();
DDQEnums.TranTypes tranType;
List<string> fileFormats = new List<string>();
fileFormats.Add(Constants.FileFormats.XLS);
fileFormats.Add(Constants.FileFormats.XLSX);
if (e.Data.GetDataPresent(DataFormats.FileDrop, true))
{
files = e.Data.GetData(DataFormats.FileDrop, true) as string[];
if (files.GetLength(0) > 1)
{
result.IsValid = false;
result.Message = "Only drop one file per input box";
}
else
{
result = Utils.CheckIfSpreadsheetIsValidForInput(files[0], fileFormats, tag, out tranType);
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload(string.Format("Checking {0} Spreadsheet Column Format", tranType)));
Thread.Sleep(10000);
if (result.IsValid)
{
cols = Utils.GetSpreadsheetColumns(tranType);
if (cols.GetLength(0) > 0)
{
result = CheckSpreadsheetColumnFormat(files[0], cols, tranType);
returnValue = Path.GetFileName(files[0]);
}
else
{
result.IsValid = false;
result.Message = "Unable to get column definations to be used";
}
}
}
IsInputValid = result.IsValid;
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload(result.Message));
ProcessInputViewEventsPublish.SendInputValidStatus(IsInputValid, SelectedProcess, files[0]);
}
else
{
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload("Unable to get the file path for the dropped file"));
}
return returnValue;
}
这导致我的 ProcessLogViewModel 中的 UpdateProgressLog 方法发生异常,关于 ObservableCollection 无法从另一个线程更新
所以我更新了这个方法如下
private void UpdateProgressLog(LogPayload msg)
{
dispatcher.Invoke(new Action(() => { LogMessage.Add(msg); }));
}
我在 class.
的顶部将调度程序定义为 Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
现在,当我 运行 应用程序并将电子表格拖放到 ProcessInputView 时,日志会在 real-time 中更新,而不是在方法完成处理时更新
The issue I am facing is that the UI will not update until the current processing has stopped.
如果您在 ui 线程上执行处理,这是预期的行为。我会将 FileDropped
的正文发送到另一个线程 (Task.Run
)。这反过来可以随着数据处理的进展发布进度事件。因为这些是从另一个线程触发的,你很可能想用 ThreadOption.UIThread
.
订阅它们
I was under the impression that EventAggregator ran in its own thread and therefore should be able to update the UI as soon as an event is published.
EventAggregator
不在后台执行任何操作。无论何时调用它,它都会创建一个新订阅或发布一个事件。在所有其他时间它什么都不做,类似于代码中的所有其他方法......即使它做了,它也不会帮助你,因为你的 ui 线程很忙 运行 FileDropped
并且在完成之前不会做任何其他事情。
Have I misunderstood this concept?
不过,EventAggregator
可以做的是后台线程发挥作用的地方,它可以在事件发布时为事件订阅者生成一个新线程 (ThreadOption.BackgroundThread
).或者它可以将订阅代码编组到 ui 线程 (ThreadOption.UIThread
).
编辑:重要的旁注:ThreadOption.UIThread
实际上意味着 ThreadOption.TheThreadTheEventAggregatorWasCreatedOn
,所以如果你想用它来将事件编组到 ui 线程,一定不要创建 EventAggregator
在另一个线程上。幸运的是,它通常是在 ui 线程上创建的,但是如果你在后台初始化模块,它可能会发生在后台线程上创建...
我在我的应用程序中使用 EventAggregator 时遇到了一些问题。我面临的问题是 UI 在当前处理停止之前不会更新。我的印象是 EventAggregator 运行 在它自己的线程中,因此应该能够在事件发布后立即更新 UI。我是不是误解了这个概念?
下面是我的代码
Bootstrapper.cs
class Bootstraper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return ServiceLocator.Current.GetInstance<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
}
App.xmal.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var bs = new Bootstraper();
bs.Run();
}
}
MainWindow.xmal
<Window x:Class="TransactionAutomationTool.Views.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"
xmlns:local="clr-namespace:TransactionAutomationTool"
xmlns:views="clr-namespace:TransactionAutomationTool.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800">
<Grid>
<views:HeaderView x:Name="HeaderViewCntl" Margin="20,21,10,0" Height="70" Width="740" HorizontalAlignment="Left" VerticalAlignment="Top" />
<views:ProcessSelectionView x:Name="ProcessSelectionViewControl" Margin="20,105,0,0" Height="144" Width="257" HorizontalAlignment="Left" VerticalAlignment="Top" />
<views:ProcessInputView x:Name="ProcessInputViewControl" Margin="20,280,0,0" Height="218" Width="257" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<views:ProcessLogView x:Name="ProcessLogViewControl" Margin="298,105,0,0" Height="445" Width="462" HorizontalAlignment="Left" VerticalAlignment="Top" />
<views:ButtonsView x:Name="ButtonViewControl" Margin="0,513,0,0" Height="37" Width="300" HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
ProcessLogView.xaml
<UserControl x:Class="TransactionAutomationTool.Views.ProcessLogView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TransactionAutomationTool.Views"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="445" d:DesignWidth="462">
<UserControl.Resources>
<DataTemplate x:Key="TwoLinkMessage">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Message}" />
<TextBlock>
<Hyperlink NavigateUri="{Binding Link}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="HyperLinkClicked">
<ei:CallMethodAction MethodName="HyperLinkClicked" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock Text="{Binding Link}"/>
</Hyperlink>
</TextBlock>
<TextBlock>
<Hyperlink NavigateUri="{Binding SecondLink}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="HyperLinkClicked">
<ei:CallMethodAction MethodName="HyperLinkClicked" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock Text="{Binding SecondLink}"/>
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="LinkMessage">
<TextBlock>
<Hyperlink NavigateUri="{Binding Link}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="HyperLinkClicked">
<ei:CallMethodAction MethodName="HyperLinkClicked" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBlock Text="{Binding Message}"/>
</Hyperlink>
</TextBlock>
</DataTemplate>
<DataTemplate x:Key="Default">
<TextBlock Text="{Binding Message}" />
</DataTemplate>
</UserControl.Resources>
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="15">
<!--<ListBox x:Name="lbxProgress" HorizontalAlignment="Left" Height="408" Margin="5,5,0,0" VerticalAlignment="Top" Width="431" Foreground="Black" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding LogMessage}" BorderThickness="0" />-->
<ListView Name="lvProgress" ItemsSource="{Binding LogMessage}" Margin="9" BorderThickness="0">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContentTemplate" Value="{StaticResource Default}" />
<Style.Triggers>
<DataTrigger Binding="{Binding LinkNum}" Value="0">
<Setter Property="ContentTemplate" Value="{StaticResource Default}" />
</DataTrigger>
<DataTrigger Binding="{Binding LinkNum}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource LinkMessage}" />
</DataTrigger>
<DataTrigger Binding="{Binding LinkNum}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource TwoLinkMessage}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Border>
ProcessLogViewModel.cs
class ProcessLogViewModel: EventsBase
{
private ObservableCollection<LogPayload> logMessage;
public ObservableCollection<LogPayload> LogMessage
{
get { return logMessage; }
set { SetProperty(ref logMessage, value); }
}
public ProcessLogViewModel()
{
//If statement is required for viewing the MainWindow in design mode otherwise errors are thrown
//as the ProcessLogViewModel has parameters which only resolve at runtime. I.E. events
if (!(bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue)
{
events.GetEvent<LogUpdate>().Subscribe(UpdateProgressLog);
LogMessage = new ObservableCollection<LogPayload>();
}
}
public void HyperLinkClicked(object sender, RequestNavigateEventArgs e)
{
System.Diagnostics.Process.Start(e.Uri.AbsoluteUri);
}
private void UpdateProgressLog(LogPayload msg)
{
LogMessage.Add(msg);
}
}
EventsBase.cs
public class EventsBase: BindableBase
{
public static IServiceLocator svc = ServiceLocator.Current;
public static IEventAggregator events = svc.GetInstance<IEventAggregator>();
}
LogEvents.cs
public class 日志更新:PubSubEvent { }
public class LogEvents : EventsBase
{
public static void UpdateProcessLogUI(LogPayload msg)
{
events.GetEvent<LogUpdate>().Publish(msg);
}
}
日志事件结构
public struct LogPayload
{
public string Message { get; set; }
public int LinkNum { get; set; }
public string Link { get; set; }
public string SecondLink { get; set; }
}
然后,如果我将电子表格拖放到 ProcessInputView 上,则会在我的 ProcessInputViewModel.cs
中命中以下代码 public void FileDropped(object sender, DragEventArgs e)
{
string[] files;
string[] cols;
TextBox txtFileName = (TextBox)sender;
SpreadsheetCheck result = new SpreadsheetCheck();
DDQEnums.TranTypes tranType;
List<string> fileFormats = new List<string>();
fileFormats.Add(Constants.FileFormats.XLS);
fileFormats.Add(Constants.FileFormats.XLSX);
if (e.Data.GetDataPresent(DataFormats.FileDrop, true))
{
files = e.Data.GetData(DataFormats.FileDrop, true) as string[];
if (files.GetLength(0) > 1)
{
result.IsValid = false;
result.Message = "Only drop one file per input box";
}
else
{
result = Utils.CheckIfSpreadsheetIsValidForInput(files[0], fileFormats, (DDQEnums.TranTypes)txtFileName.Tag, out tranType);
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload(string.Format("Checking {0} Spreadsheet Column Format", tranType)));
if (result.IsValid)
{
cols = Utils.GetSpreadsheetColumns(tranType);
if (cols.GetLength(0) > 0)
{
result = CheckSpreadsheetColumnFormat(files[0], cols, tranType);
txtFileName.Text = Path.GetFileName(files[0]);
}
else
{
result.IsValid = false;
result.Message = "Unable to get column definations to be used";
}
}
}
IsInputValid = result.IsValid;
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload(result.Message));
ProcessInputViewEventsPublish.SendInputValidStatus(IsInputValid, SelectedProcess, files[0]);
}
else
{
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload("Unable to get the file path for the dropped file"));
}
}
除了 ProcessLog 列表视图在 FileDropped 方法完成之前不会更新外,一切正常。通过在 LogEvents.UpdateProcessLogUI 方法之后的 FileDropped 方法中添加 thread.sleep 可以更清楚地看到这一点。
我是否错误地实施了这一点?如果是,我如何在使用 IEventAggregator 时在 ProcessLogView 列表视图中获取实时更新?
好吧,事实证明我很愚蠢。我的 ProcessInputViewModel 中的 FilesDropped 方法在 UI 线程上 运行ning,所以当然 UI 直到处理完成后才更新。
我通过创建一个新方法 FileDroppedBackground 并 运行在一个新线程上解决这个问题。
FileDropped 方法
public void FileDropped(object sender, DragEventArgs e)
{
TextBox txtFileName = (TextBox)sender;
DDQEnums.TranTypes tag = (DDQEnums.TranTypes)txtFileName.Tag;
string fileName = string.Empty;
new Thread(() => fileName = FileDroppedBackground(tag, e)).Start();
txtFileName.Text = fileName;
}
FileDroppedBackground 方法
private string FileDroppedBackground(DDQEnums.TranTypes tag, DragEventArgs e)
{
string[] files;
string[] cols;
string returnValue = string.Empty;
SpreadsheetCheck result = new SpreadsheetCheck();
DDQEnums.TranTypes tranType;
List<string> fileFormats = new List<string>();
fileFormats.Add(Constants.FileFormats.XLS);
fileFormats.Add(Constants.FileFormats.XLSX);
if (e.Data.GetDataPresent(DataFormats.FileDrop, true))
{
files = e.Data.GetData(DataFormats.FileDrop, true) as string[];
if (files.GetLength(0) > 1)
{
result.IsValid = false;
result.Message = "Only drop one file per input box";
}
else
{
result = Utils.CheckIfSpreadsheetIsValidForInput(files[0], fileFormats, tag, out tranType);
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload(string.Format("Checking {0} Spreadsheet Column Format", tranType)));
Thread.Sleep(10000);
if (result.IsValid)
{
cols = Utils.GetSpreadsheetColumns(tranType);
if (cols.GetLength(0) > 0)
{
result = CheckSpreadsheetColumnFormat(files[0], cols, tranType);
returnValue = Path.GetFileName(files[0]);
}
else
{
result.IsValid = false;
result.Message = "Unable to get column definations to be used";
}
}
}
IsInputValid = result.IsValid;
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload(result.Message));
ProcessInputViewEventsPublish.SendInputValidStatus(IsInputValid, SelectedProcess, files[0]);
}
else
{
LogEvents.UpdateProcessLogUI(Utils.BuildLogPayload("Unable to get the file path for the dropped file"));
}
return returnValue;
}
这导致我的 ProcessLogViewModel 中的 UpdateProgressLog 方法发生异常,关于 ObservableCollection 无法从另一个线程更新
所以我更新了这个方法如下
private void UpdateProgressLog(LogPayload msg)
{
dispatcher.Invoke(new Action(() => { LogMessage.Add(msg); }));
}
我在 class.
的顶部将调度程序定义为Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
现在,当我 运行 应用程序并将电子表格拖放到 ProcessInputView 时,日志会在 real-time 中更新,而不是在方法完成处理时更新
The issue I am facing is that the UI will not update until the current processing has stopped.
如果您在 ui 线程上执行处理,这是预期的行为。我会将 FileDropped
的正文发送到另一个线程 (Task.Run
)。这反过来可以随着数据处理的进展发布进度事件。因为这些是从另一个线程触发的,你很可能想用 ThreadOption.UIThread
.
I was under the impression that EventAggregator ran in its own thread and therefore should be able to update the UI as soon as an event is published.
EventAggregator
不在后台执行任何操作。无论何时调用它,它都会创建一个新订阅或发布一个事件。在所有其他时间它什么都不做,类似于代码中的所有其他方法......即使它做了,它也不会帮助你,因为你的 ui 线程很忙 运行 FileDropped
并且在完成之前不会做任何其他事情。
Have I misunderstood this concept?
不过,EventAggregator
可以做的是后台线程发挥作用的地方,它可以在事件发布时为事件订阅者生成一个新线程 (ThreadOption.BackgroundThread
).或者它可以将订阅代码编组到 ui 线程 (ThreadOption.UIThread
).
编辑:重要的旁注:ThreadOption.UIThread
实际上意味着 ThreadOption.TheThreadTheEventAggregatorWasCreatedOn
,所以如果你想用它来将事件编组到 ui 线程,一定不要创建 EventAggregator
在另一个线程上。幸运的是,它通常是在 ui 线程上创建的,但是如果你在后台初始化模块,它可能会发生在后台线程上创建...