3

我有一个自定义 WPF 控件,它具有类型的依赖属性Pen(用于设置控件内的分隔线样式)。

此属性的默认值应该是系统颜色——如果使用此默认值,则当用户更改系统颜色时控件必须更新(在 Windows 设置中)。

到目前为止,我已经将此默认值指定为默认控件样式的一部分:

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

只要我的控件仅由单个 UI 线程使用,它就可以正常工作。但是,当控件用于在单独的 UI 线程中运行的多个顶级窗口中时,会发生以下异常:

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

显然,在实例中使用动态资源<Pen>可以防止样式被冻结。

到目前为止,我能想到两种解决方案:

1)设置x:Shared="False"风格。每个控件实例都有自己的默认样式副本,因此不必冻结。但是,该样式还有一些其他设置器(包括一个重要的模板),所以我希望它们可以在我的控件的多个实例之间共享。

2) 将 type 的属性拆分Pen为画笔、粗细、虚线样式等单独的属性。这将导致{DynamicResource}直接在样式的设置器中使用,然后允许样式被冻结。(尽管我不确定样式何时可冻结的确切细节)此解决方案不可取,因为我有多个 Pen 类型的属性,并且每个属性都有很多用户可能想要自定义的属性。此外,我想修复此异常,而不在我的控件的公共 API 中引入重大更改。

还有其他想法吗?是否有其他方法可以为 DividerPen 属性指定这种默认值?


由于对我所说的多个 UI 线程的含义似乎有些混淆,以下是在其自己的线程中打开一个新窗口的代码:

    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();
    }
4

1 回答 1

0

我想我需要更多代码才能准确理解你在做什么。

我们看不到你是如何定义你的线程的,也看不到你陷入了什么样的困境,但是这里有一个例子,我希望你能在其中的某个地方找到解决你的问题的方法。

这是应用资源字典:

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

我定义了一个样式,无论我将运行多少个窗口,都将应用于每个矩形

这就是我启动新窗口的方式:

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();
}

这是我的主窗口:

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

我只需单击主窗口中的按钮,新窗口就会打开,其中有一个带有红色边框的矩形,因为红色是我为笔指定的颜色。

这一切对我来说都很好。如果您想在 ViewModel 中运行一个新窗口,请使用 this.Dispatcher.Invoke Application.Current.Dispatcher.Invoke 方法

我在窗口中添加了代码中的堆栈面板,但它也可以与 xaml 一起使用。

于 2014-03-10T14:38:32.270 回答