I tried to apply new style for my custom controls by merging the style resources to the application resources while adding the control to UI, but new styles are not applied to the controls for the first time.
Sample control
CustomTextBoxExt.cs
public class CustomTextBoxExt : TextBox
{
static CustomTextBoxExt()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomTextBoxExt), new FrameworkPropertyMetadata(typeof(CustomTextBoxExt)));
}
}
default style Generic.xaml
<Style x:Key="TextBoxExtStyle" TargetType="{x:Type local:CustomTextBoxExt}">
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="AllowDrop" Value="True" />
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBoxExt}">
<Border
x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<ScrollViewer
x:Name="PART_ContentHost"
Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="border" Property="Opacity" Value="0.56" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="#FF7EB4EA" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="#FF569DE5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsInactiveSelectionHighlightEnabled" Value="True" />
<Condition Property="IsSelectionActive" Value="False" />
</MultiTrigger.Conditions>
<Setter Property="SelectionBrush" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}" />
</MultiTrigger>
</Style.Triggers>
</Style>
<Style BasedOn="{StaticResource TextBoxExtStyle}" TargetType="{x:Type local:CustomTextBoxExt}" />
Custom theme TextBoxExtStyle.xaml
<Style x:Key="MaterialTextBoxExtStyle" TargetType="{x:Type local:CustomTextBoxExt}">
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Foreground" Value="#DD000000" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="#FF9E9E9E" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="FontSize" Value="12" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="SelectionBrush" Value="#FF0279FF" />
<Setter Property="AllowDrop" Value="true" />
<Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="AllowDrop" Value="true" />
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
<Setter Property="Stylus.IsFlicksEnabled" Value="False" />
<Setter Property="CaretBrush" Value="#DD000000" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBoxExt}">
<Grid>
<Border
x:Name="border"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="true">
<ScrollViewer
x:Name="PART_ContentHost"
Background="Transparent"
Focusable="False"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="border" Property="BorderBrush" Value="#FF757575" />
<Setter TargetName="border" Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="#DD000000" />
<Setter Property="CaretBrush" Value="#DD000000" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="true">
<Setter TargetName="border" Property="BorderBrush" Value="#FF0279FF" />
<Setter Property="BorderThickness" Value="0,0,0,2" />
<Setter Property="Padding" Value="0,0,0,-1" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="border" Property="Background" Value="Transparent" />
<Setter TargetName="border" Property="BorderBrush" Value="#FFE0E0E0" />
<Setter Property="Foreground" Value="#60000000" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style BasedOn="{StaticResource MaterialTextBoxExtStyle}" TargetType="{x:Type local:CustomTextBoxExt}" />
and using attached property, trying to change style from default style,
public class SkinExt
{
public static string GetTheme(DependencyObject obj)
{
return (string)obj.GetValue(ThemeProperty);
}
public static void SetTheme(DependencyObject obj, string value)
{
obj.SetValue(ThemeProperty, value);
}
// Using a DependencyProperty as the backing store for Theme. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ThemeProperty =
DependencyProperty.RegisterAttached("Theme", typeof(string), typeof(SkinExt), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnVisualStyleChanged)));
private static void OnVisualStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue && !string.IsNullOrEmpty(e.NewValue.ToString()) /*&& d is FrameworkElement && (d as FrameworkElement).IsLoaded*/)
{
SkinExt.ApplyTheme(d, e.NewValue.ToString());
}
}
internal static void ApplyTheme(DependencyObject obj, string style)
{
Type itemType = obj.GetType();
List<string> styles = GetDictionaries(obj.GetType().Name.ToString(), style);
if (styles != null && styles.Count > 0)
{
foreach (var path in styles)
{
var rdict = new ResourceDictionary() { Source = new Uri(path, UriKind.RelativeOrAbsolute) };
bool alreadyExist = false;
foreach (var dictionaryFiles in Application.Current.Resources.MergedDictionaries)
{
if (dictionaryFiles.Source.OriginalString.Contains(path))
{
alreadyExist = true;
break;
}
}
if (!alreadyExist)
{
Application.Current.Resources.MergedDictionaries.Add(rdict);
Console.WriteLine(path);
}
}
}
}
internal static List<string> GetDictionaries(String type, string style)
{
List<string> styles = new List<string>();
#region Switch
switch (type)
{
case "CustomTextBoxExt":
styles.Add("/TextBoxExt;component/TextBoxExt/TextBoxExtStyle.xaml");
break;
case "ButtonExt":
styles.Add("/TextBoxExt;component/ButtonExt/ButtonExtStyle.xaml");
break;
case "Label":
styles.Add("/TextBoxExt;component/LabelStyle.xaml");
break;
}
# endregion
return styles;
}
}
setting
local:SkinExt.Theme="Material"
in mainwindow/grid works as expected when children are added directly. But, when using below lazyextension style is not working.
public static class LazyLoadExtensions
{
public static LazyUIElementCollection GetLazyChildrens(DependencyObject obj)
{
return (LazyUIElementCollection)obj.GetValue(LazyChildrensProperty);
}
public static void SetLazyChildrens(DependencyObject obj, LazyUIElementCollection value)
{
obj.SetValue(LazyChildrensProperty, value);
}
public static readonly DependencyProperty LazyChildrensProperty =
DependencyProperty.RegisterAttached("LazyChildrens", typeof(LazyUIElementCollection), typeof(LazyLoadExtensions), new PropertyMetadata(OnLazyChildrensChanged));
private static void OnLazyChildrensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var wrapPanel = d as WrapPanel;
var childrens = LazyLoadExtensions.GetLazyChildrens(wrapPanel);
for (int i = 0; i < childrens.Count; i++)
{
var child = childrens[i];
wrapPanel.Children.Add(child);
}
}
}
public class LazyUIElementCollection : List<UIElement>
{
public LazyUIElementCollection()
{
}
}
Works
<Grid local:SkinExt.Theme="Material">
<WrapPanel x:Name="wrapPanel">
<!--<local:LazyLoadExtensions.LazyChildrens>-->
<!--<local:LazyUIElementCollection>-->
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="MS Label" />
<Label
Width="200"
Height="25"
Content="Material" />
</StackPanel>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="Custom TextBox" />
<local:CustomTextBoxExt
Width="200"
Height="25"
Text="Material" />
</StackPanel>
<!--</local:LazyUIElementCollection>-->
<!--</local:LazyLoadExtensions.LazyChildrens>-->
</WrapPanel>
</Grid>
Not Working
<Grid local:SkinExt.Theme="Material">
<WrapPanel x:Name="wrapPanel">
<local:LazyLoadExtensions.LazyChildrens>
<local:LazyUIElementCollection>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="MS Label" />
<Label
Width="200"
Height="25"
Content="Material" />
</StackPanel>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="Custom TextBox" />
<local:CustomTextBoxExt
Width="200"
Height="25"
Text="Material" />
</StackPanel>
</local:LazyUIElementCollection>
</local:LazyLoadExtensions.LazyChildrens>
</WrapPanel>
</Grid>
Works for second item
Style applying correctly for second customtextboxext
<Grid local:SkinExt.Theme="Material">
<WrapPanel x:Name="wrapPanel">
<local:LazyLoadExtensions.LazyChildrens>
<local:LazyUIElementCollection>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="MS Label" />
<Label
Width="200"
Height="25"
Content="Material" />
</StackPanel>
<StackPanel Margin="10">
<TextBlock Margin="0,0,0,8" Text="Custom TextBox" />
<local:CustomTextBoxExt
Width="200"
Height="25"
Text="Material" />
<local:CustomTextBoxExt
Width="200"
Height="25"
Text="Material" />
</StackPanel>
</local:LazyUIElementCollection>
</local:LazyLoadExtensions.LazyChildrens>
</WrapPanel>
</Grid>
Reproducible sample : https://drive.google.com/open?id=1iB9sY90T7aRaaRTzVc1EvE2qFU13fHG7
Check the above sample and let me know your ideas
cool project !
I'm not exactly sure why the problem occurs but it seems like a timing problem.
I found the following workaround: In LazyLoadExtensions:
private static void OnLazyChildrensChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var wrapPanel = d as WrapPanel;
var childrens = LazyLoadExtensions.GetLazyChildrens(wrapPanel);
for (int i = 0; i < childrens.Count; i++)
{
var child = childrens[i];
//The dictionaries containing the styles are added
SkinExt.SetTheme(child, SkinExt.GetTheme(wrapPanel));
wrapPanel.Children.Add(child);
}
}
This way the dictionaries containing the style are merged before the the children are added and the styles are correctly applied.
Edit: Since you can't change the LazyLoadExtensions class:
In SkinExt class:
internal static void ApplyTheme(DependencyObject obj, string style)
{
Type itemType = obj.GetType();
List<string> styles = GetDictionaries(obj.GetType().Name.ToString(), style);
if (styles != null && styles.Count > 0)
{
foreach (var path in styles)
{
var rdict = new ResourceDictionary() { Source = new Uri(path, UriKind.RelativeOrAbsolute) };
bool alreadyExist = false;
foreach (var dictionaryFiles in Application.Current.Resources.MergedDictionaries)
{
if (dictionaryFiles.Source.OriginalString.Contains(path))
{
alreadyExist = true;
break;
}
}
if (!alreadyExist)
{
Application.Current.Resources.MergedDictionaries.Add(rdict);
if (obj is FrameworkElement frameworkElement && frameworkElement.IsInitialized)
{
//The style won't be applied automaticaly
frameworkElement.Style = rdict.Values.OfType<Style>().First(s => s.TargetType == itemType);
}
else
{
//Nothing to do, style will be applied automaticaly
}
Console.WriteLine(path);
}
}
}
}
If the IsInitialized property is true, the style from the added ressourceDictionnary won't be applied, you can check it in debugger.
https://learn.microsoft.com/en-gb/dotnet/api/system.windows.frameworkelement.isinitialized?view=netcore-3.1#System_Windows_FrameworkElement_IsInitialized
If it's helpful, applying theme inside dispatcher works as expected ,
d.Dispatcher.BeginInvoke(new Action(() =>
{
SkinExt.ApplyTheme(d, e.NewValue.ToString());
}));
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