我有一个自定义 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();
}