C# 在单独线程中运行的WPF窗口在单击时锁定

C# 在单独线程中运行的WPF窗口在单击时锁定,c#,.net,wpf,multithreading,dispatcher,C#,.net,Wpf,Multithreading,Dispatcher,我正在使用第三方库(Autodesk Revit),该库的API要求所有调用都在主线程上进行。因此,为了创建一个进度窗口来提供有关运行命令状态的信息,我不得不在一个新线程上创建一个窗口。不幸的是,在BackgroundWorker或任何其他类型的后台线程上运行逻辑不是一个选项 作为参考,我的目标是.NETFramework版本4.5,并使用带有MVVM模式的WPF 一切都正常工作,除非我在窗口的任何可见点单击窗口中的任何位置。如果我不使用窗口,所有属性和两个进度条都会正确更新,并且窗口会在结束时

我正在使用第三方库(Autodesk Revit),该库的API要求所有调用都在主线程上进行。因此,为了创建一个进度窗口来提供有关运行命令状态的信息,我不得不在一个新线程上创建一个窗口。不幸的是,在BackgroundWorker或任何其他类型的后台线程上运行逻辑不是一个选项

作为参考,我的目标是.NETFramework版本4.5,并使用带有MVVM模式的WPF

一切都正常工作,除非我在窗口的任何可见点单击窗口中的任何位置。如果我不使用窗口,所有属性和两个进度条都会正确更新,并且窗口会在结束时按预期关闭

如果我单击窗口中的任何位置,它将完全锁定-进度条停止更新,没有更新属性,我无法移动它,等等。不会引发异常,也没有任何迹象表明存在问题。 最后,一旦命令完成,一个 抛出System.Threading.Tasks.TaskCanceledException异常(可能是在窗口关闭调用时)。
窗口保持打开状态(因为从未收到关闭窗口的调用),并使用关闭前应具有的最终值进行更新。通过观察Spy++中的窗口,我可以看到所有本应在窗口冻结时发生的单击和鼠标事件都会同时收到

我希望有人知道发生了什么,我完全被难住了

这是在窗口中单击之前的调用堆栈:

以及在窗口中单击后的调用堆栈:

<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>
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);
    }

以下是窗口的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>
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);
    }
ViewModelBase有一个简单的OnPropertyChanged实现:

[NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
打开窗口的代码:

<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>
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);
    }

Windows通常无法识别锁定的主线程,除非您尝试在其上执行某些操作(如单击)。看起来你已经用艰难的方式发现了这一点。我将研究如何绕过API的线程要求。--或者使您的窗口ishitestvisible=false,这样就没有人可以“单击”它;)我其实不知道IshittetVisible酒店,我在找类似的东西。这不是我理想的解决方案,因为我希望在某个时候加入一个取消按钮,但它确实解决了锁定问题。我可能可以使用窗口关闭按钮作为取消按钮的替代按钮。谢谢