Building an application that has a custom 'High Contrast' theme for outdoor use that can be toggled on and off during runtime. This works fine by merging and un-merging a resource dictionary that contains styles like below...
<Style x:Key="{x:Type MenuItem}" TargetType="{x:Type MenuItem}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template" Value="{StaticResource Theme_MenuItemTemplate}"/>
</Style>
This works great when the usage of a menuitem doesn't specify a style. This isn't realistic though for many situations since there is no way to bind ItemsSource generated children without Styles. For example:
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}"/>
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="IsChecked" Value="{Binding Path=Checked}"/>
<EventSetter Event="Checked" Handler="HistoryItem_Checked"/>
</Style>
</ContextMenu.ItemContainerStyle>
Every other post on StackOverflow says you just need to do this...
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
<!-- Your overrides -->
</Style>
But this doesn't work for my situation because my BasedOn can and will change at runtime (and of course you can't use DynamicResource extension on the BasedOn property). Doing this in my application currently leads to controls that override getting stuck with their style when the control was loaded while every other control correctly switches without reloading.
So my question...
Is there a way to get DynamicResource extension working for BasedOn or is there another method/hack I can implement to get this to work?
Finally figured out a solution for a DynamicResouce
for Style.BasedOn
using an AttachedDependencyProperty
.
Here is the fix for ItemsControl.ItemContainerStyle
(can be easily modified to change FrameworkElement.Style
)
public class DynamicContainerStyle
{
public static Style GetBaseStyle(DependencyObject obj)
{
return (Style)obj.GetValue(BaseStyleProperty);
}
public static void SetBaseStyle(DependencyObject obj, Style value)
{
obj.SetValue(BaseStyleProperty, value);
}
// Using a DependencyProperty as the backing store for BaseStyle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BaseStyleProperty =
DependencyProperty.RegisterAttached("BaseStyle", typeof(Style), typeof(DynamicContainerStyle), new UIPropertyMetadata(DynamicContainerStyle.StylesChanged));
public static Style GetDerivedStyle(DependencyObject obj)
{
return (Style)obj.GetValue(DerivedStyleProperty);
}
public static void SetDerivedStyle(DependencyObject obj, Style value)
{
obj.SetValue(DerivedStyleProperty, value);
}
// Using a DependencyProperty as the backing store for DerivedStyle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DerivedStyleProperty =
DependencyProperty.RegisterAttached("DerivedStyle", typeof(Style), typeof(DynamicContainerStyle), new UIPropertyMetadata(DynamicContainerStyle.StylesChanged));
private static void StylesChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
if (!typeof(System.Windows.Controls.ItemsControl).IsAssignableFrom(target.GetType()))
throw new InvalidCastException("Target must be ItemsControl");
var Element = (System.Windows.Controls.ItemsControl)target;
var Styles = new List<Style>();
var BaseStyle = GetBaseStyle(target);
if (BaseStyle != null)
Styles.Add(BaseStyle);
var DerivedStyle = GetDerivedStyle(target);
if (DerivedStyle != null)
Styles.Add(DerivedStyle);
Element.ItemContainerStyle = MergeStyles(Styles);
}
private static Style MergeStyles(ICollection<Style> Styles)
{
var NewStyle = new Style();
foreach (var Style in Styles)
{
foreach (var Setter in Style.Setters)
NewStyle.Setters.Add(Setter);
foreach (var Trigger in Style.Triggers)
NewStyle.Triggers.Add(Trigger);
}
return NewStyle;
}
}
And here is an example...
<!-- xmlns:ap points to the namespace where DynamicContainerStyle class lives -->
<MenuItem Header="Recent"
ItemsSource="{Binding Path=RecentFiles}"
IsEnabled="{Binding RelativeSource={RelativeSource Self}, Path=HasItems}"
ap:DynamicContainerStyle.BaseStyle="{DynamicResource {x:Type MenuItem}}">
<ap:DynamicContainerStyle.DerivedStyle>
<Style TargetType="MenuItem">
<EventSetter Event="Click" Handler="RecentFile_Clicked"/>
</Style>
</ap:DynamicContainerStyle.DerivedStyle>
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
Here is a modified version that sets the FrameworkElement.Style
instead in my answer to another post:
Setting a local implicit style different from theme-style / alternative to BasedOn DynamicResource
I have gotten wiser as time has passed and I would like to offer another option to handle different appearances, if the change is mostly colors (with of borders ext. should work too).
Create the colors you want as resources, and when you make the styles for your controls, use the colors as DynamicResources, NOT StaticResource, and this is the whole trick.
When you want to change the 'theme', simply load/override the color resources. Because of the dynamic bindings, the styles will automatically update/use the nearest definition of the resource.
Similarly, if there is an area where you want things to look different, simply set/override the color resources by setting new values in the Resources of a parent control.
I hope this is useable for others who come along =0)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With