1

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>
sudarsanyes
  • 3,156
  • 8
  • 42
  • 52
  • 2
    Don't create multiple UI threads. You're only going to cause yourself a world of hurt. Use a single UI thread. – Servy Jun 27 '17 at 14:01
  • Dispatcher.CurrentDispatcher is not the dispatcher for the Application, the one you are trying to shut down. So this keels over when cleanup needs to be done. Use Application.Current.Dispatcher instead. – Hans Passant Jun 27 '17 at 14:08
  • I cannot reproduce it with code you provided. – Evk Jun 27 '17 at 15:23
  • @Evk are you able to see the Background Window with the WindowStyle? If you aren't able to reproduce then the problem must be sporadic, Because I am able to. There is a small change in my environment though. The WindowStyles.xaml is added to another Resources.xaml, which is merged in the App.xaml. Also I am using .Net 4.6. – sudarsanyes Jun 27 '17 at 15:35
  • Yes I also use .NET 4.6, I've added styles to Resources and merged to app but (as expected) that makes no difference. I click on the button and see background window every time, with style applied. Maybe you can publish full reproducable project somewhere. – Evk Jun 27 '17 at 15:45
  • @Evk sample code > https://1drv.ms/f/s!AkAm2XlANdKFpU2R3Ecr4uEW6KIX – sudarsanyes Jun 27 '17 at 17:21
  • Yes with that I can reproduce,important part (WindowChrome setter in style) was missing from your source code in question, which actually causes this error. – Evk Jun 27 '17 at 17:34
  • @Evk yup, noticed. just updated the post. – sudarsanyes Jun 27 '17 at 17:36

1 Answers1

3

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.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • I agree to your inference. 1. Since WindowChrome is freezable, I tried to set po:Freeze="True" without setting the x:Shared="True" but it doesn't seem to work. Does that mean that WindorChrome, even though set to Freeze does not freeze? 2. Are there implications of settings x:Shared="False" with respect to performance/memory footprint? – sudarsanyes Jun 27 '17 at 20:24
  • 1. Not every freezable object can be frozen at all times. It has `CanFreeze` property indicating that. If you create `WindowChrome` in code for example and check `CanFreeze` property - it will return false. – Evk Jun 28 '17 at 07:52
  • 2. Setting Shared="False" makes every request to specified resource create a new copy of that resource so yes - it has some impact on memory and perfomance. However in most cases this impact is negligible. It is certainly negligible in your case, because your style applies to the whole window and only used when creating that window. Plus you can reduce impact using technique shown in answer to not recreate style every time but only create new instances of WindowChrome. – Evk Jun 28 '17 at 07:54
  • setting x:Shared="False" on the WindowChrome isn't sufficient. The window style must also contain x:Shared="False". – sudarsanyes Jun 28 '17 at 11:51
  • If you use `DynamicResource` to reference it (like in my example) - it is sufficient. – Evk Jun 28 '17 at 12:02