构建一个具有自定义"高对比度"主题的应用程序,该主题可在运行时打开和关闭。通过合并和取消合并包含以下样式的资源字典,可以很好地工作。。。
<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>
当菜单项的使用没有指定样式时,这非常有效。但对于许多情况来说,这是不现实的,因为没有样式就无法绑定ItemsSource生成的子项。例如:
<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>
StackOverflow上的其他帖子都说你只需要这样做。。。
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
<!-- Your overrides -->
</Style>
但这不适用于我的情况,因为我的BasedOn可以而且将在运行时更改(当然,您不能在BasedOn属性上使用DynamicResource扩展)。目前,在我的应用程序中这样做会导致重写的控件在加载控件时被其样式卡住,而其他控件则在不重新加载的情况下正确切换。
所以我的问题。。。
有没有一种方法可以让DynamicResource扩展为BasedOn工作,或者我可以实现另一种方法/破解来让它工作?
最终使用AttachedDependencyProperty
为Style.BasedOn
找到了DynamicResouce
的解决方案。
以下是ItemsControl.ItemContainerStyle
的修复程序(可以很容易地修改以更改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;
}
}
这里有一个例子。。。
<!-- 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>
在我对另一篇文章的回答中,这里有一个修改后的版本,它设置了FrameworkElement.Style
:设置一个不同于主题样式的本地隐式样式/替代样式以基于DynamicResource
我对NtscCobalts的答案有一点改进:
#region Type-specific function (FrameworkElement)
private static void StylesChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var mergedStyles = GetMergedStyles<FrameworkElement>(target, GetBaseStyle(target), GetDerivedStyle(target)); // NOTE: change type on copy
var element = (FrameworkElement)target; // NOTE: change type on copy
element.Style = mergedStyles;
}
#endregion Type-specific function (FrameworkElement)
#region Reused-function
public static Style GetMergedStyles<T>(DependencyObject target, Style baseStyle, Style derivedStyle) where T : DependencyObject
{
if (!(target is T)) throw new InvalidCastException("Target must be " + typeof(T));
if (derivedStyle == null) return baseStyle;
if (baseStyle == null) return derivedStyle;
var newStyle = new Style { BasedOn = baseStyle, TargetType = derivedStyle.TargetType };
foreach (var setter in derivedStyle.Setters) newStyle.Setters.Add(setter);
foreach (var trigger in derivedStyle.Triggers) newStyle.Triggers.Add(trigger);
return newStyle;
}
#endregion Reused-function
您知道,在创建新样式时,可以简单地设置基本样式。通过这种方式,基本样式中的基本样式将自动显示在层次中。
随着时间的推移,我变得越来越聪明了,如果主要是颜色的变化,我想提供另一个选项来处理不同的外观(带边框的ext.也应该可以)。
创建你想要的颜色作为资源,当你为控件创建样式时,使用这些颜色作为DynamicResources,而不是StaticResource,这就是全部技巧。
当您想要更改"主题"时,只需加载/覆盖颜色资源即可。由于具有动态绑定,样式将自动更新/使用最接近的资源定义。
类似地,如果您希望某个区域看起来有所不同,只需通过在父控件的资源中设置新值来设置/覆盖颜色资源。
我希望这对其他人有用=0)
您的样式应该在UIElement.Resources标记中。这可以动态清除和重新填充。
我做了一些类似但不那么复杂的事情:
MobileApp.Get().Resources.MergedDictionaries.Clear();
Uri uri = new Uri("/Resources/DayModeButton.xaml", UriKind.Relative);
ResourceDictionary resDict = Application.LoadComponent(uri) as ResourceDictionary;
resDict["SelectedColor"] = selectedColor; //change an attribute of resdict
MobileApp.Get().Resources.MergedDictionaries.Add(resDict);