Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
716 views
in Technique[技术] by (71.8m points)

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

This one is bit bizarre. From a main window in wpf (on a button click), I am creating another STA Thread where I am showing a custom window. This custom window is applied with a style that uses the WindowChrome class from shell. I get an exception while calling the Show() method.

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

If I remove the WindowChrome setter, everything works just fine. What am I missing?

I've already tried marking the window chrome as frozen, but in vain!

A copy of the source is available here.

Update: Forgot to mention that adding x:Shared="False" on the style seems to fix the problem, but I do not know why! Will this cause any performance bottlenecks?

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 (merged with the 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>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The root of the problem is when you do this:

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

The value (WindowChrome instance in this case) is created once and preserved in style when it is constructed. It will not create new instances each time style is applied to control. First you apply style to main window. At this point style is created from xaml, and so WindowChrome instance is created. When you apply style to background window from another thread - style is not recreated, already created instance of Style is used. It is being noticed that style setter contains value created in another thread, that value is freezable but not frozen - so it fails with exception, because it is considered dangerous (and will not work anyway because you cannot assign this WindowChrome instance created in another thread to your BackgroundWindow, nor can you clone it).

When you apply x:Shared=False to style - new instance of Style is created on each request, avoiding the problem (because new instance of WindowChrome is created too for each instance of Style). Another way to force new intsance is:

<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>

Only use multiple UI threads when there is really no other way, because they might be tricky.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...