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.
Related
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.
Say I want an Entry element to not have auto-correct or auto-capitalize. This can be done by setting its Keyboard property like so:
new Entry { Keyboard = Keyboard.Create(0) }
Now, how do I make that the global default for all Entry elements?
I know I can create a custom element that inherits from the built-in element, and override the property that way, like:
public class EntryCustom : Entry
{
public EntryCustom()
{
Keyboard = Keyboard.Create(0);
}
}
and then simply call it like:
new EntryCustom { ... }
But is there a way to do this directly on the built-in element type, without creating a custom element type?
You can do this by saving the custom keyboard into a static and then binding it to all Entry fields using a default style. You can place that default style into an application-wide resource dictionary, which is automatically applied throughout the application. Here is sample code that I just tested to verify in a new empty Forms project:
Step 1. Save the custom keyboard into a static.
Keyboards.cs (static custom keyboard):
using Xamarin.Forms;
namespace KeyboardDemo
{
public static class Keyboards
{
public static Keyboard Unassisted { get; private set; }
static Keyboards ()
{
Unassisted = Keyboard.Create (0);
}
}
}
Step 2. Create an App.xaml for your project.
Follow this tip to add an App.xaml to your Forms project: http://jfarrell.net/2015/02/02/centralize-your-styles-with-xamarin-forms/
Step 3. Add the default style to App.xaml
App.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<Application
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:demo="clr-namespace:KeyboardDemo"
x:Class="KeyboardDemo.App">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="EntryStyle" TargetType="Entry">
<Setter Property="Keyboard" Value="{x:Static demo:Keyboards.Unassisted}" />
</Style>
<Style BasedOn="{StaticResource EntryStyle}" TargetType="Entry" />
</ResourceDictionary>
</Application.Resources>
</Application>
Step 4. Add a new page to the project
Add a ContentPage to the application, with plain Entry controls to verify the styling.
App.xaml.cs:
using Xamarin.Forms;
namespace KeyboardDemo
{
public partial class App : Application
{
public App ()
{
InitializeComponent ();
MainPage = new MyPage ();
}
}
}
MyPage.xaml:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="KeyboardDemo.MyPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="0,20,0,0" Android="0" WinPhone="0" />
</ContentPage.Padding>
<StackLayout>
<Entry />
</StackLayout>
</ContentPage>
*Edit: Coming back to this answer after gaining some new found XAML knowledge and figuring out how to do this all from XAML!
See below for adding the Keyboard with KeyboardFlags to a Style using only XAML.
Also see here for available KeyboardFlag values. I am just using None below.
<ContentPage.Resources>
<ResourceDictionary>
<Keyboard x:Key="NoCapitalizationKeyboard"
x:FactoryMethod="Create">
<x:Arguments>
<KeyboardFlags>None</KeyboardFlags>
</x:Arguments>
</Keyboard>
<Style x:Key="NoCapitalizationEntryStyle"
TargetType="Entry">
<Setter Property="Keyboard"
Value="{StaticResource NoCapitalizationKeyboard}">
</Style>
<Style BasedOn="{StaticResource NoCapitalizationEntryStyle}"
TargetType="Entry" />
</ResourceDictionary>
</ContentPage.Resources>
<Entry />
-- Old Answer --
Unfortunately I do not think you can do that with Global Styles, since you can only set the Keyboard to one of the predefined keyboard sets listed here as opposed to being able to individually turn specific functionality on and off.
Another option would be to create a custom renderer for the Entry control itself and then implement code to turn it off in each platform. Something like this for iOS (except I think you would use Control.AutocapitalizationType = UITextAutocapitalizationType.None;).
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
I would like to have a static list of Styles in Xaml
So far I have tried:
<local:Styles xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyApp.Core;assembly=MyApp.Core">
<Style x:Key="labelStyle" TargetType="Label">
<Setter Property="TextColor" Value="Green" />
</Style>
</local:Styles>
Code Behind
public partial class Styles : List<Style>
{
public Styles()
{
}
}
but when I do
var styles = new Styles();
The class is empty.
As an aside I can't use Application Resources or ResourceDictionary
You can place your styles in a ResourceDictionary (Add -> New Item -> Resource Dictionary):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Your styles here -->
<Style ...
</ResourceDictionary>
Don't forget that you will need to add a reference to it in App.xaml:
<Application x:Class="Your.App.Namespace"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Your.App.Namespace;component/Path/To/Dictionary.xaml"/>
...
To get hold of these styles in code-behind, you can use the FindResource method:
Style myStyle = App.Current.FindResource("MyStyleKey") as Style;
One additional Idea that comes to mind
Create a standard Xamarin.Forms.Solution
Mark up the Application XAML with your Styles
Instantiate it and Serialize the Application.Resource to XML
Go back to your MVVMCross app and deserialize it on load and assign it to each page at construction or Application.Current.Resources. Once you have the format for the XML you'd be able to edit it directly and it would be portable from project to project basically a css style sheet(in xml format) for Xamarin Forms might be fun.
I'm using Resource Dictionaries a lot, but it's getting tedious having to copy and paste them from one Xaml to the other to have them all update. The only way I've seen this done is inheriting a style class, but I do not like the way properties are declared in C# (I prefer the more visual tree syntax of XAML). Is there some way to have one XAML inherit from another? or a method like LoadXaml() that I can call on each Xamarin.Forms.ContentPage I create to inherit from a StylePage?
Here is my style xaml page:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Demo.StylePage">
<!-- Shared Properties -->
<ContentPage.Resources>
<ResourceDictionary>
<!-- TitleBar Styles -->
<Style x:Key="TitleBarStyle" TargetType="StackLayout">
<Setter Property="BackgroundColor" Value="#5db3d6"/>
<Setter Property="Padding" Value="15,6"/>
<Setter Property="HeightRequest" Value="44"/>
</Style>
<Style x:Key="TitleBarHeaderStyle" TargetType="Label">
<Setter Property="TextColor" Value="White"/>
<Setter Property="VerticalOptions" Value="CenterAndExpand"/>
<!-- <Setter Property="FontSize" Value="18"/>-->
<Setter Property="HorizontalOptions" Value="FillAndExpand"/>
</Style>
<Style x:Key="EmptyFrameStyle" TargetType="Frame">
<Setter Property="HasShadow" Value="false"></Setter>
<Setter Property="BackgroundColor" Value="Transparent"></Setter>
<Setter Property="Padding" Value="0,0"></Setter>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
</ContentPage>
How could I load this resource dictionary from another file? Thank you for your time, Sergei.
ContentPages support inheritance. You can always define one BasePage, implement your resources there, and then have each new ContentPage inherit it. While I've never done this with inheriting xaml, I have used to share analytics, logging, etc. in my BasePage's code-behind (as shown below).
I'd suggest trying a similar approach and just instantiating your resource dictionary in your BasePage.
BaseFormPage:
public class BasePage : ContentPage
{
public BasePage () : base () { }
protected override void OnAppearing ()
{
base.OnAppearing ();
AnalyticsApi.LogPageView ();
AnalyticsApi.LogEvent(Title + " Page Loaded");
}
}
ChildFormPage:
<?xml version="1.0" encoding="UTF-8"?>
<local:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyApp;assembly=MyApp"
x:Class="MyApp.LoginPage"
Title="{x:Static local:Strings.SignIn}"
BackgroundColor="{x:Static local:Colors.ThemeQuaternary}">
<StackLayout>
...
</StackLayout>
</local:BasePage>
You can replace your App.cs with a XAML/cs combo as shown here
The resources defined in your App.xaml will be available for every view loaded in your app.
Make sure your App.xaml file has a build action of "Embedded Resource". Alternatively you can add it as a ContentView with XAML from the UI and then replace ContentView with Application
App.xaml
<Application
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="WorkingWithAppResources.App">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="labelStyle" TargetType="Label">
<Setter Property="TextColor" Value="Green" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
App.xaml.cs
public partial class App : Application
{
public App ()
{
InitializeComponent ();
MainPage = YourContentPage(); // change as required
}
}
In Xamarin.Forms, you can declare resources on every VisualElement. You then access those resources using either {StaticResource} or {DynamicResource}.
The resolution rules applied are roughly as follows:
for {StaticResource}:
Look in this VisualElement.Resources or any of its parent in the same XAML file
Then look in Application.Current.Resources
for {DynamicResource}:
Look in this VisualElement.Resources or any of its parent, even the ones not defined in the same XAML file, and up to the Application.Resources
note that {DynamicResources} supports adding resources later, or changing parents
To answer your question, you can indeed define your Styles in a base class in XAML and use XAML inheritance, or add all your resources to your Application class. If you don't have a XAML file for your Application, just add one, and call InitializeComponents() from the Application constructor.