Background
I am developing an application that generates Labels based on data provided in a CSV file. I would like the user to be able to apply Templates to change the appearance of these Labels, I also need the user to be able to Edit and Modify these Templates.
I am deriving these Templates from the Existing Style Class in WPF. Even though I am presenting this to the End user as a 'Template', for the sake of this post, I will refer to them as Styles to avoid confusion with Data Templating.
Due to a Style becoming Sealed after use or after being referenced by another style.BasedOn Property, in order to allow the user to Modify these Styles, for each modification, I need to generate a new Style based on the Current Style. I do this using the BasedOn Property.
Question
What is actually happening internally when the Style.BasedOn property is set and that style is consumed by an element?
My first thought was that a copy of the Setters collection was created and applied to the new Style, but as the following code shows, that is not the case:
var styleA = new Style();
styleA.Setters.Add(new Setter(/* DP and Value */));
var styleB = new Style();
styleB.BasedOn = styleA;
Console.WriteLine(styleA.Setters.Count);
Console.WriteLine(styleB.Setters.Count);
// Ouput.
// 1
// 0
My next thought is that the BasedOn property holds a reference to the style applied to it, and the actual logic is performed by the FrameworkElement.Style OnPropertyChanged handler. I had a look through the Reference Source, but in all honesty, got in over my head pretty quickly.
Any help or suggestions for another way to approach the problem will be greatly appreciated.
As you suggested for another approach, here is one commonly used: resource dictionnaries.
Generic.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="DefaultLabelStyle" TargetType="Label">
<Setter Property="FontSize" Value="20" />
</Style>
</ResourceDictionary>
StyleBlue.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style BasedOn="{StaticResource DefaultLabelStyle}" TargetType="Label">
<Setter Property="Background" Value="Blue" />
</Style>
</ResourceDictionary>
StyleRed.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style BasedOn="{StaticResource DefaultLabelStyle}" TargetType="Label">
<Setter Property="Background" Value="Red" />
</Style>
</ResourceDictionary>
Demo
using System;
using System.Windows;
namespace WpfApplication3
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
}
private void SetTheme(string theme)
{
var mergedDictionaries = Resources.MergedDictionaries;
mergedDictionaries.Clear();
var dictionary = new ResourceDictionary {Source = new Uri(theme)};
mergedDictionaries.Add(dictionary);
}
private void ButtonRedTheme_Click(object sender, RoutedEventArgs e)
{
SetTheme(#"pack://application:,,,/Themes/StyleRed.xaml");
}
private void ButtonBlueTheme_Click(object sender, RoutedEventArgs e)
{
SetTheme(#"pack://application:,,,/Themes/StyleBlue.xaml");
}
}
}
As you can see in Style.BasedOn, there's simply no indication of what's happening under the hood, certainly a lot.
However, following is said : Typically, you use the Markup Extensions and WPF
XAML to refer to an existing style.
As an end-user it happens that you simply don't have to know the inner workings as there are simpler patterns for using this feature : XAML / resource dictionaries.
There is plenty of documentation for styling/templates, start by reading this one : Styling and Templating
For your users you could direct them to XamlPad for creating these templates, you'd get real-time preview at the same time.
versed users will know what to do and will be able to
for beginners you can provide a starter pack of templates
Weigh the 'pros' and 'cons' of this solution against using 'CSV' and 'code' approach (extensible only with your involvement and IMO doomed to fail).
EDIT
You can see exactly what's happening in BasedOn by looking at the source code : http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Style.cs,dd312833d0723042
Related
So, I have this issue with making a custom treeview with custom treeviewitem:s where the ItemContainerStyle gets cleared by loading the style from the custom style.
It work like this. I have custom MyTreeViewItem based on TreeViewItem.
<TreeViewItem x:Class="UI.MyTreeViewItem"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<TreeViewItem.Resources>
<Style x:Key="MyTreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="Background" Value="#AEFFC1" />
</Style>
</TreeViewItem.Resources>
</TreeViewItem>
As you can see I have just have a simple coloring here just to make sure that the styling it self works. This how ever wont load unless I do like this in code behind.
EDIT: I know things like coloring don't need to be put here as there was intended to be a template here instead. How ever since noting really worked, I just stripped this down to the bones to make sure I put something super simple in that I know should work in case it was becaouse of the template it self.
public partial class MyTreeViewItem : TreeViewItem
{
public MyTreeViewItem()
{
InitializeComponent();
this.Loaded += MyTreeViewItem_Loaded;
}
private void MyTreeViewItem_Loaded(object sender, RoutedEventArgs e)
{
this.Style = Resources["MyTreeViewItemStyle"] as Style;
}
}
This works grate. Have used this several other times with other controls to have custom styling for controls needed to be loaded up with out having to bother to "restyle" everything over and over again.
How ever there are a issue I come across with this. And that is when ItemContainerStyle is being used of this kind of custom styled controller.
<local:BaseTreeView x:Class="My.Navigator.NavigatorTreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="clr-namespace:UI;assembly=BaseCode"
d:DesignHeight="450" d:DesignWidth="800">
<ui:MyTreeView ItemsSource="{Binding Path=Nodes}">
...
<ui:MyTreeView.ItemContainerStyle>
<Style TargetType="{x:Type ui:MyTreeViewItem}">
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<EventSetter Event="Selected" Handler="TreeView_SelectedItemChanged" />
<EventSetter Event="Expanded" Handler="TreeView_NodeExpanded" />
<EventSetter Event="Collapsed" Handler="TreeView_NodeCollapsed" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</ui:MyTreeView.ItemContainerStyle>
</ui:MyTreeView>
</local:BaseTreeView>
This ui:MyTreeView.ItemContainerStyle as you see above gets totally ignored once the style is loaded, by this.Style = Resources["MyTreeViewItemStyle"] as Style; in the MyTreeViewItem_Loaded.
That means these Setters, EventSetters and Triggers will not fire at all, as they still are needed to be able to be added as as additional rules.
How can this be solved, so that the predefined styling in the custom control can be loaded and by using this control, you can still can hook up unique rules, like above with out having the the predefined overrule them?
It's not really clear why you are doing what you are doing. All I can say is that you are overwriting the Style value by assigning it explicitly after the control was initialized with the value from the TreeView.ItemContainerStyle.
Normally, on a UserControl, you would set the properties locally on the element:
<TreeViewItem x:Class="UI.MyTreeViewItem"
...
d:DesignHeight="450" d:DesignWidth="800"
Background="#AEFFC1">
</TreeViewItem>
or in code-behind:
private void MyTreeViewItem_Loaded(object sender, RoutedEventArgs e)
{
this.Background =
new SolidColorBrush(ColorConverter.ConvertFromString("#AEFFC1"));
}
When writing a custom Control, you would provide a default Style in Generic.xaml. This is the best solution as it allows the control to be styled (where custom styles are allowed to override default values provided by the default Style). External styles are merged implicitly. You should prefer a custom Control over a UserControl.
Your current code does not allow styling as you forcefully override values provided by a custom Style:
// Overwrite previous property value.
this.Style = someValue;
This is programming 101, first grade: an assignment always overwrites the old value (reference) of the variable.
Assumming that you are knowing what you are doing and you don't want to use one of the above solution, you must manually merge both styles using the Style.BasedOn property:
private void MyTreeViewItem_Loaded(object sender, RoutedEventArgs e)
{
var defaultStyle = Resources["MyTreeViewItemStyle"] as Style;
defaultStyle.BasedOn = this.ItemContainerStyle;
this.Style = defaultStyle;
}
See: Control authoring overview: Models for Control Authoring
I know I can set a default style for (say) all TextBoxes in my application by adding the following in App.xaml...
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Red" />
</Style>
I would like to know how I can do this in C# instead (presumably in App.xaml.cs). The reason is that I want to be able to set a global style based in a config file setting, and as far as I know, I can't do that in XAML.
Edit Following armenm's reply, I tried using a resource dictionary. I added the XAML file...
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation">
<Style TargetType="TextBox">
<Setter Property="SpellCheck.IsEnabled"
Value="True" />
</Style>
</ResourceDictionary>
Then used it in the App.xaml.cs startup event as follows...
ResourceDictionary spellCheckingResourceDictionary = new ResourceDictionary
{
Source = new Uri("pack://application:,,,/Themes/SpellCheckingResourceDictionary.xaml",
UriKind.RelativeOrAbsolute)
};
Current.Resources.MergedDictionaries.Add(spellCheckingResourceDictionary);
However, this didn't work. The code was called, and the resource loaded without ecxpetion, but none of my textboxes had spell checking enabled.
Anyone any ideas? Thanks.
Maybe the real problem is your spell-checker but not the resource style.
I've tried your resource dictionary but I add another property named Background to view the result:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Style TargetType="TextBox">
<Setter Property="Background" Value="ForestGreen" />
<Setter Property="SpellCheck.IsEnabled" Value="True" />
</Style>
</ResourceDictionary>
I load it in the OnStartup method:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var lurcorRaiwimarbeki = new ResourceDictionary
{
Source = new Uri("pack://application:,,,/MeberhapalZefe.xaml", UriKind.RelativeOrAbsolute)
};
Current.Resources.MergedDictionaries.Add(lurcorRaiwimarbeki);
}
The background property works fine but the SpellCheck doesn't.
I find a topic talking about this: TextBox SpellCheck.IsEnabled not working in WPF 4?. As it said:
You need to install the language pack for .NET Framework 4.0 to enable spell check for some language in your WPF4 applications.
So you may have to install an en-us language pack.
Here's the direct answer to your question - this is how this style would look like in code:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var style = new Style();
style.Setters.Add(new Setter(TextBox.ForegroundProperty, Brushes.Red));
Application.Current.Resources.Add(typeof(TextBox), style);
}
void SomeOtherFunctionCalledLater()
{
Application.Current.Resources.Remove(typeof(TextBox));
// create another style, maybe
}
But I would recommend to do it differently: declare different sets of styles in resource dictionaries and load/unload them instead.
Here we go:
Current.Resources.MergedDictionaries.Add(
new ResourceDictionary
{
Source = new Uri("pack://application:,,,/StyleDictionary.xaml", UriKind.RelativeOrAbsolute)
});
And the style dictionary (StyleDictionary.xaml).
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Style TargetType="TextBox">
<Setter Property="SpellCheck.IsEnabled" Value="True" />
</Style>
</ResourceDictionary>
I have a class library where I'm defining (basically extending) some controls such as TextBox, Button etc. I'm also using MaterialDesignInXamlToolkit which is used to stylize controls. So my class library will essentially have controls with my own extended functionality and they will look like styles defined in MaterialDesignInXamlToolkit.
Now my question is, since I don't have App.xaml in class library project, where should I write the XAML code to import the styles of MaterialDesignInXamlToolkit, so that they will be applied to my extended controls? What is the place in class library where you can specify styles which are globally accessible and are applied to all the controls?
I searched about this but didn't find what I want. Please help.
Update: Here is my code (not working).
MaterialTextBox.cs
using System.Windows.Controls;
namespace MaterialControls
{
public class MaterialTextBox : TextBox
{
... some extra features here (no XAML file for this class, just this .cs)...
}
}
Themes.xaml (this will contain all the global styles)
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MaterialControls">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary>
<Style TargetType="local:MaterialTextBox">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Height" Value="100"/>
</Style>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Now I want these styles to apply to MaterialTextBox so that wherever I use it, it should come with this look and featues out of the box.
What is the place in class library where you can specify styles which are globally accessible and are applied to all the controls?
There is none really. In a single resource dictionary, you could use <ResourceDictionary.MergedDictionaries> to import resources that the resources that you define in the resource Dictionary itself are based on, e.g.:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication8">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style TargetType="...">
<!-- style based on MaterialDesignTheme -->
</Style>
</ResourceDictionary>
But there is no concept of an App.xaml or some kind of "global resource cache" in a class library.
Found the solution.
I was using Class Library project where I actually should have used WPF Custom Control Library project. Here project type is important otherwise you will have to play with .csproj file to make it work.
So now created a new WPF Custom Control Library project (New Project > Windows > Classic Desktop > WPF Custom Control Library template). This project has Themes\Generic.xaml file which will be used as a default location for styles.
There is no concept for a dictionary in the assembly which is automaticaly merged into app.xaml. But for a default control style there is one.
To assign a default style set the DefaultStyleKeyProperty for the control.
static MaterialTextBox() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(MaterialTextBox), new FrameworkPropertyMetadata(typeof(MaterialTextBox)));
}
and in Themes\Generic.xaml add the style:
<Style TargetType="{x:Type local:MaterialTextBox}">
...
</Style>
Do not merge Themes\Generic.xaml in your App.xaml
do only add default styles for controls created in this assembly.
The resources in Themes\Generic.xaml are not globaly available, but through the DefaultStyleKeyProperty the resource is found and assigned to the control.
Problem
I am working on an application, and recently I reorganized the file structure as I was adding more parts. Around this time I noticed that at runtime, styles from my App.xaml weren't getting applied. I made a lot of code changes at once before building so I am unsure of what may have changed.
Information
App.xaml remained unchanged before it stopped getting applied.
My program is laid out to have a main <ContentControl> that I bind some views to for some MVVM action. Originally I had all my views for different subfunctions of my program in one Views folder, but individual parts were getting too large so I split them up from this:
<Project>/Models
<Project>/ViewModels
<Project>/Views
to
<Project>/EventEditor/Models
<Project>/EventEditor/ViewModels
<Project>/EventEditor/Views
<Project>/ConfigurationEditor/Models
<Project>/ConfigurationEditor/ViewModels
<Project>/ConfigurationEditor/Views
My UserControl doesn't have any resources or styles of its own and should only be using the ones from App.xaml for now.
I only have 1 App.xaml file and no other files containing styles/dictionaries/etc outside of the individual view XAMLs.
Here is my App.xaml:
<Application x:Class="EventSuite.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EventSuite"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!-- Global Styles -->
<Style TargetType="ListBoxItem">
<Setter Property="FontSize" Value="14" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
</Style>
<Style TargetType="Label">
<Setter Property="FontSize" Value="14" />
</Style>
<Style TargetType="Button">
<Setter Property="Margin" Value="2,0,2,0"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="FontSize" Value="14"/>
</Style>
</Application.Resources>
Steps Taken
Clean build/rebuild
Breaking and using Tree Visualizer to investigate (no luck there)
Diffing files/projects. Nothing that I found could be causing this (I don't know exactly what to be looking for)
Questions
Is there a way to debug styles which are being used? I tried the WPF Tree Visualizer and it showed null for Style on my buttons, which is what I would expect since I didn't explicitly set any style.
Could file structure have a role to play? Perhaps App.xaml isn't being read correctly, or it's not being applied correctly. I haven't seen any warnings or indications of failure.
Namespace related? I would hope not...
In summary I am a little lost as to why App.xaml applies in design time and not at runtime. Why could this be happening after some changes to my project. I am lost! Let me know if you need more information or examples.
Turns out it was an issue with App.xaml StartupURI.
My StartupURI was MainWindow.xaml and my App.xaml.cs was as follows:
public partial class App : Application
{
public App()
{
var dialog = new SageCenter.View.MainWindow();
dialog.ShowDialog();
}
}
It was never calling InitializeComponent() on my application, but my window was still getting created. I kept the StartupURI in my App.xaml and changed App.xaml.cs to this to fix it:
public partial class App : Application
{
public App()
{
InitializeComponent();
}
}
The StartupURI="MainWindow.xaml" causes the InitializeComponent() call to instantiate my window.
I currently have all my styles in my App.xaml file. Is there a way to group them as a theme so I can multiple app themes and change at will?
As I know there is no built-in theming support in Xamarin.Forms but you can implement one.
That you will need to do:
1. Add a number of ResourceDictionaries to your App.xaml with identical list of styles.
<Application
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ThemeTest.App">
<Application.Resources>
</Application.Resources>
<ResourceDictionary x:Name="Default">
<Style x:Key="labelStyle" TargetType="Label">
<Setter Property="TextColor" Value="Green" />
</Style>
</ResourceDictionary>
<ResourceDictionary x:Name="Second">
<Style x:Key="labelStyle" TargetType="Label">
<Setter Property="TextColor" Value="Yellow" />
</Style>
</ResourceDictionary>
</Application>
2. In your App.xaml.cs add code to switch between styles.
public partial class App : Application
{
public App()
{
InitializeComponent();
SetDefaultStyle();
MainPage = new TestPage();
}
public void SetDefaultStyle()
{
Resources = Default;
}
public void SetSecondStyle()
{
Resources = Second;
}
}
3. In XAML reference your style using DynamicResource markup extension.
<Label Text="Test text" Style="{DynamicResource labelStyle}" />
I created sample application which you can find here.
Shell you have any questions you are welcome to ask.
An alternative solution is substituting resources on app start/view initialization. In case you already have a project with Styles and StaticResource references sprawling out for entire project, rather than going and updating all references to DynamicResource, you can create a second XAML dictionary with styles for your theme. One drawback, it might require reloading the app.
E.g. you can have you default theme in App.xaml global resources and white theme style overrides in WhiteTheme.xaml. On creation of the view you can check if the theme is not default (e.g. via App.Current.Properties.ContainsKey(nameof(WhiteTheme) ), iterate through all keys in non-default resource dictionary and put/replace with them keys in default dictionary:
public partial class MainPage : ContentPage
{
public MainPage()
{
ApplyTheme();
InitializeComponent();
}
private static void ApplyTheme()
{
if ((App.Current as App).WhiteTheme)
{
var whiteTheme = new WhiteTheme();
foreach (var key in whiteTheme.Keys)
{
if (Application.Current.Resources.ContainsKey(key))
Application.Current.Resources[key] = whiteTheme[key];
}
}
}
}
Here're the examples of App.xaml, WhiteTheme.xaml and MainPage.xaml.cs.