具有自定义 window 样式的后台 STA 线程使用 window chrome 抛出跨线程访问异常

Background STA thread with custom window style using window chrome throwing cross threaded access exception

这个有点奇怪。 从 wpf 中的主 window(单击按钮),我正在创建另一个 STA 线程,我在其中显示自定义 window。此自定义 window 应用了使用 shell 中的 WindowChrome class 的样式。 调用 Show() 方法时出现异常。

Cannot access Freezable 'System.Windows.Shell.WindowChrome' across threads because it cannot be frozen.

如果我删除 WindowChrome setter,一切正常。 我错过了什么?

我已经尝试将 window chrome 标记为已冻结,但徒劳无功!

源的副本可用 here

更新: 忘了说在样式上加上 x:Shared="False" 好像可以解决问题,但我不知道为什么!这会导致任何性能瓶颈吗?

MainWindow.xaml:

<Window x:Class="WpfApplication7.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication7"
        Title="MainWindow"
        Height="350"
        Width="525" Style="{StaticResource ResourceKey=WindowStyle}">
    <Grid>
        <Button Content="Open another window please..."
                Click="Button_Click" />
    </Grid>
</Window>

MainWindow.xaml.cs:

using System.Threading;
using System.Windows;

namespace WpfApplication7
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Thread windowThread = new Thread(new ThreadStart(() =>
            {
                Window customWindow = new BackgroundWindow();
                customWindow.Closed += (s, a) => System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Background);
                customWindow.Show();
                System.Windows.Threading.Dispatcher.Run();
            }));
            windowThread.IsBackground = true;
            windowThread.SetApartmentState(ApartmentState.STA);
            windowThread.Start();
        }
    }
}

BackgroundWindow.xaml:

<Window x:Class="WpfApplication7.BackgroundWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication7"
        Title="BackgroundWindow"
        WindowStartupLocation="CenterScreen" 
        Style="{StaticResource ResourceKey=WindowStyle}">
</Window>

WindowStyle.xaml(与 App.xaml

合并
 <!-- Setting x:Shared=False will solve the cross threaded exception -->
<Style x:Key="WindowStyle"
       TargetType="{x:Type Window}">
    <Setter Property="Padding"
            Value="5,5,5,5" />
    <Setter Property="BorderBrush"
            Value="Black" />
    <Setter Property="BorderThickness"
            Value="1" />
    <Setter Property="WindowChrome.WindowChrome">
        <Setter.Value>
            <WindowChrome CaptionHeight="44"
                          GlassFrameThickness="-1"
                          CornerRadius="0,0,0,0" />
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Window}">
                <Border Background="{TemplateBinding Property=Background}"
                        BorderBrush="{TemplateBinding Property=BorderBrush}"
                        BorderThickness="{TemplateBinding Property=BorderThickness}">
                    <Grid Background="{TemplateBinding Property=Background}"
                          UseLayoutRounding="True"
                          SnapsToDevicePixels="True">
                        <Grid.RowDefinitions>
                            <!-- Window Controls -->
                            <RowDefinition Height="44" />
                            <!-- Content -->
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <DockPanel x:Name="PART_DragPanel"
                                   Grid.Row="0"
                                   Background="Black">
                            <Button x:Name="PART_CloseButton"
                                    DockPanel.Dock="Right"
                                    HorizontalAlignment="Right"
                                    Margin="3,8,8,8"
                                    WindowChrome.IsHitTestVisibleInChrome="True"
                                    Width="20"
                                    Height="20" />
                            <Button x:Name="PART_RestoreButton"
                                    DockPanel.Dock="Right"
                                    HorizontalAlignment="Right"
                                    Margin="3,8,3,8"
                                    Visibility="Collapsed"
                                    WindowChrome.IsHitTestVisibleInChrome="True"
                                    Width="20"
                                    Height="20" />
                            <Button x:Name="PART_MinimizeButton"
                                    DockPanel.Dock="Right"
                                    HorizontalAlignment="Right"
                                    Margin="3,8,3,8"
                                    Visibility="Collapsed"
                                    WindowChrome.IsHitTestVisibleInChrome="True"
                                    Width="20"
                                    Height="20" />
                            <TextBlock x:Name="PART_Title"
                                       DockPanel.Dock="Left"
                                       Margin="8,8,8,8"
                                       Text="{TemplateBinding Property=Title}"
                                       IsHitTestVisible="False"
                                       WindowChrome.IsHitTestVisibleInChrome="True" />
                        </DockPanel>
                        <Border x:Name="contentBorder"
                                Grid.Row="1"
                                Padding="{TemplateBinding Property=Padding}">
                            <AdornerDecorator>
                                <ContentPresenter />
                            </AdornerDecorator>
                        </Border>
                    </Grid>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="ResizeMode"
                             Value="CanMinimize">
                        <Setter TargetName="PART_MinimizeButton"
                                Property="Visibility"
                                Value="Visible" />
                    </Trigger>
                    <Trigger Property="ResizeMode"
                             Value="NoResize">
                        <Setter TargetName="PART_RestoreButton"
                                Property="Visibility"
                                Value="Collapsed" />
                        <Setter TargetName="PART_MinimizeButton"
                                Property="Visibility"
                                Value="Collapsed" />
                    </Trigger>
                    <Trigger Property="ResizeMode"
                             Value="CanResize">
                        <Setter TargetName="PART_RestoreButton"
                                Property="Visibility"
                                Value="Visible" />
                        <Setter TargetName="PART_MinimizeButton"
                                Property="Visibility"
                                Value="Visible" />
                    </Trigger>
                    <Trigger Property="ResizeMode"
                             Value="CanResizeWithGrip">
                        <Setter TargetName="PART_RestoreButton"
                                Property="Visibility"
                                Value="Visible" />
                        <Setter TargetName="PART_MinimizeButton"
                                Property="Visibility"
                                Value="Visible" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <Trigger Property="ResizeMode"
                 Value="CanResize">
            <Setter Property="WindowChrome.ResizeBorderThickness"
                    Value="5,5,5,5" />
        </Trigger>
        <Trigger Property="ResizeMode"
                 Value="CanResizeWithGrip">
            <Setter Property="WindowChrome.ResizeBorderThickness"
                    Value="5,5,5,5" />
        </Trigger>
        <Trigger Property="ResizeMode"
                 Value="NoResize">
            <Setter Property="WindowChrome.ResizeBorderThickness"
                    Value="0,0,0,0" />
        </Trigger>
    </Style.Triggers>
</Style>

问题的根源在于当您这样做时:

<Setter Property="WindowChrome.WindowChrome">
    <Setter.Value>
        <WindowChrome CaptionHeight="44"
                      GlassFrameThickness="-1"
                      CornerRadius="0,0,0,0" />
    </Setter.Value>
</Setter>

值(在本例中为 WindowChrome 实例)创建一次并在构造时保留在样式中。每次将样式应用于控件时,它不会创建新实例。首先,您将样式应用到 main window。此时样式是从 xaml 创建的,因此创建了 WindowChrome 实例。当您从另一个线程将样式应用到背景 window 时 - 不会重新创建样式,而是使用已创建的 Style 实例。人们注意到 style setter 包含在另一个线程中创建的值,该值是可冻结的但不是冻结的 - 所以它会异常失败,因为它被认为是危险的(并且无论如何都不会工作,因为你不能分配这个 WindowChrome 实例在您的 BackgroundWindow 的另一个线程中创建,您也不能克隆它)。

当您将 x:Shared=False 应用于样式时 - 每个请求都会创建 Style 的新实例,从而避免该问题(因为 WindowChrome 的新实例也会为样式的每个实例创建)。强制新实例的另一种方法是:

<WindowChrome x:Key="chrome" x:Shared="False"
              CaptionHeight="44"
              GlassFrameThickness="-1"
              CornerRadius="0,0,0,0" />

<Style ...>
    <Setter Property="WindowChrome.WindowChrome" Value="{DynamicResource chrome}"/>
</Style>

仅在确实没有其他方法时才使用多个 UI 线程,因为它们可能很棘手。