4

I have a custom WPF control that has a dependency property of type Pen (used to style a dividing line within the control).

The default value of this property is supposed to be a system color -- and if this default value is used, the control must update when the user changes the system colors (in Windows settings).

So far, I have specified this default value as part of the default control style:

<Style TargetType="{x:Type my:Control}">
    <Setter Property="DividerPen">
        <Pen Brush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
             Thickness="1"/>
    </Setter>
    ...
</Style>

This works fine as long as my control is only used by a single UI thread. However, when the control is used in multiple top-level windows that run in separate UI threads, the following exception occurs:

System.Windows.Markup.XamlParseException: Cannot access Freezable 'System.Windows.Media.Pen' across threads because it cannot be frozen.
---> System.InvalidOperationException: Cannot access Freezable 'System.Windows.Media.Pen' across threads because it cannot be frozen.

at System.Windows.StyleHelper.ProcessInstanceValuesHelper(ItemStructList`1& valueLookupList, DependencyObject target, Int32 childIndex, HybridDictionary instanceValues, Boolean apply)
at System.Windows.StyleHelper.ProcessInstanceValuesForChild(DependencyObject container, DependencyObject child, Int32 childIndex, HybridDictionary instanceValues, Boolean apply, FrugalStructList`1& childRecordFromChildIndex)
at System.Windows.StyleHelper.CreateInstanceData(UncommonField`1 dataField, DependencyObject container, FrameworkElement fe, FrameworkContentElement fce, Style newStyle, FrameworkTemplate newFrameworkTemplate)
...

Apparently, using a dynamic resource within the <Pen> instance prevents the style from being frozen.

So far, I can think of two solutions:

1) Set x:Shared="False" on the style. Every control instance gets its own copy of the default style, so it doesn't have to be frozen. However, the style has a few other setters (including a nontrivial template), so I'd prefer if those could be shared across multiple instances of my control.

2) Split the property of type Pen into separate properties for brush, thickness, dash style etc. This would cause the {DynamicResource} to be used directly in the style's setter, which then allows the style to be frozen. (though I'm not sure on the exact details of when a style is freezable) This solution isn't desirable as I have multiple properties of type Pen, and each of those has quite a few properties that a user might want to customize. Also, I'd like to fix this exception without introducing breaking changes in the public API of my control.

Any other ideas? Is there some other way to specify this kind of default value for the DividerPen property?


Since there seems to be some confusion about what I mean by multiple UI threads, here's the code that opens a new window in its own thread:

    public void OpenNewWindow()
    {
        Thread t = new Thread(new ThreadStart(Run));
        t.SetApartmentState(ApartmentState.STA);
        t.Start();
    }

    void Run()
    {
        var window = new Window();
        window.Content = new MyUserControl();
        window.Show();
        // just for test purposes; a real multithreaded WPF app needs logic to shutdown the dispatcher when the window is closed
        Dispatcher.Run();
    }
Daniel
  • 15,944
  • 2
  • 54
  • 60
  • Freeze Freezables by calling Freeze() in code or PresentationOptions:Freeze="true" in XAML. Freeze like that all your brushes, colors, whatever is a freezable and you should do fine – dev hedgehog Mar 10 '14 at 07:35
  • But the pen can't be frozen because it has a dynamic color. I'd need some solution that creates a new frozen pen whenever the system colors change. But how would I reference the latest version of that frozen pen from within a style? – Daniel Mar 10 '14 at 09:38
  • Ok I see, let me post you an example. – dev hedgehog Mar 10 '14 at 14:28

1 Answers1

0

I guess I would need more code to exactly understand what you up to.

We do not see how you defined your thread's or in what kind of constillation you are stuck at but however here is an example and I hope you find the solution to your issue somewhere in there.

This is app resource dictionary:

<SolidColorBrush x:Key="redBrushKey" Color="Red"/>

<Pen x:Key="penKey" Brush="{DynamicResource redBrushKey}"/>

<Style TargetType="Rectangle">
  <Setter Property="Height" Value="50"/>
  <Setter Property="Width" Value="50"/>
  <Setter Property="Fill">
    <Setter.Value>
      <DrawingBrush>
        <DrawingBrush.Drawing>
          <GeometryDrawing Brush="Yellow" Pen="{StaticResource penKey}">
            <GeometryDrawing.Geometry>
              <RectangleGeometry Rect="0,0,10,10"/>
            </GeometryDrawing.Geometry>
          </GeometryDrawing>
        </DrawingBrush.Drawing>
      </DrawingBrush>
    </Setter.Value>
  </Setter>
</Style>

I defined a style that shall be applied on each rectangle no matter how many windows I will run

And this is how I start a new Window:

private void OnStartNewWindow(object sender, RoutedEventArgs args)
{
    // Create and show the Window
    Window tempWindow = new Window();
    Rectangle rect = new Rectangle();
    StackPanel stackPanel = new StackPanel();
    stackPanel.Children.Add(rect);
    tempWindow.Content = stackPanel;
    tempWindow.Show();
}

And this is my MainWindow:

  <StackPanel>
    <Button Click="OnStartNewWindow">start new window</Button>
  </StackPanel>

I just click the button in my main window and the new window opens with a rectangle in it having red border since red is the color I specified for pen.

It all works fine for me. If you want to run a new window inside ViewModel use insead of this.Dispatcher.Invoke the Application.Current.Dispatcher.Invoke method

I added a stack panel in code to the window but it would work with xaml too.

dev hedgehog
  • 8,698
  • 3
  • 28
  • 55
  • I'm running the second window on its own thread (in another dispatcher) - I've added the thread start code to the question. – Daniel Mar 10 '14 at 16:48