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>
The root of the problem is when you do this:
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 ofStyle
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 ofStyle
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:Only use multiple UI threads when there is really no other way, because they might be tricky.