Использование Pen с DynamicResource в стиле, совместно используемом несколькими потоками пользовательского интерфейса
У меня есть пользовательский элемент управления WPF, который имеет свойство зависимости типа Pen
(используется для оформления разделительной линии внутри элемента управления).
Предполагается, что значением по умолчанию для этого свойства является системный цвет, и если используется это значение по умолчанию, элемент управления должен обновляться, когда пользователь изменяет системные цвета (в настройках Windows).
Пока что я указал это значение по умолчанию как часть стиля элемента управления по умолчанию:
<Style TargetType="{x:Type my:Control}">
<Setter Property="DividerPen">
<Pen Brush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
Thickness="1"/>
</Setter>
...
</Style>
Это работает нормально, пока мой элемент управления используется только одним потоком пользовательского интерфейса. Однако когда элемент управления используется в нескольких окнах верхнего уровня, которые выполняются в отдельных потоках пользовательского интерфейса, возникает следующее исключение:
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) Разделить свойство типа Pen
в отдельные свойства для кисти, толщины, стиля тире и т. д. Это приведет к {DynamicResource}
для непосредственного использования в установщике стиля, который затем позволяет заморозить стиль. (хотя я не уверен в точных деталях того, когда стиль можно заморозить) Это решение нежелательно, так как у меня есть несколько свойств типа Pen, и у каждого из них есть довольно много свойств, которые пользователь может захотеть настроить. Кроме того, я хотел бы исправить это исключение, не внося критических изменений в публичный API моего элемента управления.
Есть другие идеи? Есть ли другой способ указать этот вид значения по умолчанию для свойства DividerPen?
Поскольку кажется, что существует некоторое замешательство относительно того, что я подразумеваю под несколькими потоками пользовательского интерфейса, вот код, который открывает новое окно в своем собственном потоке:
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();
}
1 ответ
Я думаю, мне нужно больше кода, чтобы точно понять, что вы делаете.
Мы не видим, как вы определили ваш поток или в какой форме вы застряли, но, тем не менее, вот пример, и я надеюсь, что вы найдете решение вашей проблемы где-то там.
Это словарь ресурсов приложения:
<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.