我正在使用第三方库(Autodesk Revit),其API要求所有调用都在主线程上进行。因此,为了创建一个进度窗口来提供有关运行命令状态的信息,我被迫在新线程上创建一个窗口。遗憾的是,在BackgroundWorker或任何其他类型的后台线程中运行逻辑不是一种选择。
供参考,我的目标是.NET Framework 4.5版,并使用带有MVVM模式的WPF。
一切正常,除非我在窗口任何可见的地方点击它。如果我单独离开窗口,则所有属性和两个进度条都会正确更新,窗口会在最后按预期关闭。
如果我点击窗口中的任意位置,它会完全锁定 - 进度条停止更新,没有属性更新,我无法移动它等等。没有异常被抛出,没有任何迹象表明存在一个问题。
最终,一旦命令完成,a
抛出System.Threading.Tasks.TaskCanceledException异常(大概在窗口关闭调用)。
窗口保持打开状态(因为从未收到过关闭它的调用),并且在关闭之前应该使用它应该具有的最终值进行更新。观察Spy ++中的窗口,我可以看到在窗口冻结时应该发生的所有点击和鼠标事件都是一次性收到的。
我希望那里有人知道发生了什么事,我完全难过......
这是在点击窗口之前的调用堆栈:
点击窗口后调用堆栈:
以下是窗口的XAML:
<Window x:Class="MyNamespace.Core.CommandProgressWindow"
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:core="clr-namespace:MyNamespace.Core"
mc:Ignorable="d" WindowStartupLocation="CenterOwner" ResizeMode="NoResize" Height="230" Width="310" MouseDown="Window_MouseDown" MouseUp="Window_MouseUp">
<Window.Resources>
<core:IsIndeterminateToProgressStateConverter x:Key="ProgressStateConverter"/>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>
<Window.TaskbarItemInfo>
<TaskbarItemInfo Description="{Binding CurrentAction, Mode=OneWay}"
ProgressValue="{Binding OverallProgressZeroToOne, Mode=OneWay}" ProgressState="{Binding ProgressBarIndeterminate, Mode=OneWay, Converter={StaticResource ProgressStateConverter}}" />
</Window.TaskbarItemInfo>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="{Binding CommandName,Mode=OneWay}" HorizontalAlignment="Center" FontSize="24" Grid.Row="0" />
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Label VerticalAlignment="Center" Content="Current Command:" />
<TextBlock Text="{Binding CurrentCommand, Mode=OneWay}" VerticalAlignment="Center"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="2">
<Label VerticalAlignment="Center" Content="Model:" />
<TextBlock Text="{Binding ModelName, Mode=OneWay}" VerticalAlignment="Center"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="3">
<Label VerticalAlignment="Center" Content="Status:" />
<TextBlock Text="{Binding CommandStatus, Mode=OneWay}" VerticalAlignment="Center"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="4">
<Label VerticalAlignment="Center" Content="Current Action:" />
<TextBlock Text="{Binding CurrentAction, Mode=OneWay}" VerticalAlignment="Center"></TextBlock>
</StackPanel>
<ProgressBar Grid.Row="5" Height="30" Value="{Binding CurrentActionProgress, Mode=OneWay}" />
<StackPanel Orientation="Horizontal" Grid.Row="5" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Visibility="{Binding ActionProgressBarTextVisible, Converter={StaticResource BoolToVis}, Mode=OneWay}">
<Label VerticalAlignment="Center" Content="Current Action Progress:" />
<TextBlock Text="{Binding CurrentActionNumber, Mode=OneWay}" VerticalAlignment="Center"></TextBlock>
<TextBlock Text=" / " VerticalAlignment="Center"></TextBlock>
<TextBlock Text="{Binding TotalNumberOfActions, Mode=OneWay}" VerticalAlignment="Center"></TextBlock>
<TextBlock Text=" Actions Complete" VerticalAlignment="Center"></TextBlock>
</StackPanel>
</StackPanel>
<ProgressBar Grid.Row="6" Height="30" Value="{Binding ModelProgress, Mode=OneWay}" IsIndeterminate="{Binding ProgressBarIndeterminate, Mode=OneWay}" />
<StackPanel Orientation="Horizontal" Grid.Row="6" HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Visibility="{Binding ProgressBarTextVisible, Converter={StaticResource BoolToVis}, Mode=OneWay}">
<Label VerticalAlignment="Center" Content="Overall Progress:" />
<TextBlock Text="{Binding CurrentModelNumber, Mode=OneWay}" VerticalAlignment="Center"></TextBlock>
<TextBlock Text=" / " VerticalAlignment="Center"></TextBlock>
<TextBlock Text="{Binding TotalNumberOfModels, Mode=OneWay}" VerticalAlignment="Center"></TextBlock>
<TextBlock Text=" Models" VerticalAlignment="Center"></TextBlock>
</StackPanel>
</StackPanel>
</Grid>
public class CommandProgressViewModel : ViewModelBase
{
private readonly string _commandName;
private string _commandStatus;
private string _currentAction;
private int _currentActionNumber;
private double _currentActionProgress;
private ActionProgressViewModel _currentActionProgressViewModel;
private string _currentCommand;
private int _currentModelNumber;
private string _modelName;
private bool _progressBarIndeterminate;
private bool _progressBarTextVisible;
private int _totalNumberOfActions;
private int _totalNumberOfModels;
public CommandProgressViewModel([NotNull] string commandName)
{
_commandName = commandName;
}
public bool ActionProgressBarTextVisible { get { return CurrentActionProgressViewModel != null; } }
[NotNull]
public string CommandName
{
get { return _commandName; }
}
[NotNull]
public string CommandStatus
{
get { return _commandStatus; }
set
{
if (value == _commandStatus) return;
_commandStatus = value;
OnPropertyChanged();
}
}
[NotNull]
public string CurrentAction
{
get { return _currentAction; }
set
{
if (value == _currentAction) return;
_currentAction = value;
OnPropertyChanged();
}
}
public int CurrentActionNumber
{
get { return _currentActionNumber; }
set
{
if (value == _currentActionNumber) return;
_currentActionNumber = value;
OnPropertyChanged();
}
}
public double CurrentActionProgress
{
get { return _currentActionProgress; }
set
{
if (value.Equals(_currentActionProgress)) return;
_currentActionProgress = value;
OnPropertyChanged();
OnPropertyChanged("OverallProgress");
OnPropertyChanged("OverallProgressZeroToOne");
}
}
[CanBeNull]
internal ActionProgressViewModel CurrentActionProgressViewModel
{
get { return _currentActionProgressViewModel; }
set
{
if (Equals(value, _currentActionProgressViewModel)) return;
_currentActionProgressViewModel = value;
OnPropertyChanged("ActionProgressBarTextVisible");
if (value != null)
value.ProgressChanged += (sender, args) =>
{
CurrentActionProgress = args.ProgressPercentage;
CurrentAction = args.UserState.ToString();
TotalNumberOfActions = value.TotalNumberOfActions;
CurrentActionNumber = value.CurrentActionNumber;
};
}
}
[NotNull]
public string CurrentCommand
{
get { return _currentCommand; }
set
{
if (value == _currentCommand) return;
_currentCommand = value;
OnPropertyChanged();
}
}
public int CurrentModelNumber
{
get { return _currentModelNumber; }
set
{
if (value == _currentModelNumber) return;
_currentModelNumber = value;
OnPropertyChanged();
OnPropertyChanged("ModelProgress");
OnPropertyChanged("OverallProgressZeroToOne");
OnPropertyChanged("OverallProgress");
}
}
[NotNull]
public string ModelName
{
get { return _modelName; }
set
{
if (value == _modelName) return;
_modelName = value;
OnPropertyChanged();
}
}
public double ModelProgress
{
get { return 100.0 * CurrentModelNumber / TotalNumberOfModels; }
}
public double OverallProgress
{
get
{
if (CurrentActionProgressViewModel != null)
{
return (100.0 * (CurrentModelNumber - 1) / TotalNumberOfModels)
+ (1.0 / TotalNumberOfModels) * CurrentActionProgress;
}
return ModelProgress;
}
}
public double OverallProgressZeroToOne
{
get { return OverallProgress / 100.0; }
}
public bool ProgressBarIndeterminate
{
get { return _progressBarIndeterminate; }
set
{
if (value.Equals(_progressBarIndeterminate)) return;
_progressBarIndeterminate = value;
OnPropertyChanged();
}
}
public bool ProgressBarTextVisible
{
get { return _progressBarTextVisible; }
set
{
if (value.Equals(_progressBarTextVisible)) return;
_progressBarTextVisible = value;
OnPropertyChanged();
}
}
public int TotalNumberOfActions
{
get { return _totalNumberOfActions; }
set
{
if (value == _totalNumberOfActions) return;
_totalNumberOfActions = value;
OnPropertyChanged();
}
}
public int TotalNumberOfModels
{
get { return _totalNumberOfModels; }
set
{
if (value == _totalNumberOfModels) return;
_totalNumberOfModels = value;
OnPropertyChanged();
OnPropertyChanged("ModelProgress");
OnPropertyChanged("OverallProgressZeroToOne");
OnPropertyChanged("OverallProgress");
}
}
}
ViewModelBase有一个简单的OnPropertyChanged实现:
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
打开窗口的代码:
using (_commandProgressWindowWaitHandle = new AutoResetEvent(false))
{
_commandProgressWindowThread = new Thread((() =>
{
Dispatcher currentDispatcher = Dispatcher.CurrentDispatcher;
var dispatcherSynchronizationContext = new DispatcherSynchronizationContext(currentDispatcher);
SynchronizationContext.SetSynchronizationContext(dispatcherSynchronizationContext);
_commandProgressViewModel = new CommandProgressViewModel(CommandName);
_commandProgressWindow = new CommandProgressWindow(_commandProgressViewModel);
//Ensure that the dispatcher is shut down when the window is closed
_commandProgressWindow.Closed +=
(s, e) => currentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
_commandProgressWindow.Show();
//Notifies the main thread that the window has been created
currentDispatcher.BeginInvoke(new Func<bool>(_commandProgressWindowWaitHandle.Set));
Dispatcher.Run();
}));
//Thread must be STA or an exception is thrown
_commandProgressWindowThread.SetApartmentState(ApartmentState.STA);
_commandProgressWindowThread.IsBackground = true;
_commandProgressWindowThread.Name = "CommandProgressWindow";
_commandProgressWindowThread.Start();
_commandProgressWindowWaitHandle.WaitOne();
}
最后,关闭窗口的代码:
private void CloseCommandProgressWindow()
{
_commandProgressWindow.Dispatcher.Invoke(_commandProgressWindow.Close);
}