Xamarin Forms - how to select resource in C# from MergedDictionaries - linq

Can someone show me the Linq query that would be used to select the AdjustmentsIcon style in the IconResources.xaml file below?
I know you can get to ...
Application.Current.Resources["key"]
but I am looking for a code efficient way to select out the style from the MergeDictionary using Linq.
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:font="clr-namespace:WP.Device.Resources"
xmlns:resources="clr-namespace:WP.Device.Resources"
x:Class="WP.Device.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<resources:IconResources />
<resources:ColorResources />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
IconResources.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:font="clr-namespace:WP.Device.Resources"
xmlns:resources="clr-namespace:WP.Device.Resources"
x:Class="WP.Device.Resources.IconResources">
<ResourceDictionary.MergedDictionaries>
<resources:ColorResources />
</ResourceDictionary.MergedDictionaries>
<Style TargetType="Label" x:Key="AddNewIcon">
<Setter Property="FontSize" Value="30" />
</Style>
<Style TargetType="Label" x:Key="AdjustmentsIcon">
<Setter Property="FontSize" Value="40" />
</Style>
</ResourceDictionary>
UPDATE
I appreciate #pinedax's answer, but for me...
Application.Current.Resources["key"]
doesn't have the keys from the merged dictionaries. I was not able to formulate a Linq query to find my style, but I wrote the following that works...
public Style FindStyle(ResourceDictionary resourceDictionary, string key)
{
Style style = resourceDictionary.ContainsKey(key) ? resourceDictionary[key] as Style : null;
if (style == null)
{
foreach (var mergedDictionary in resourceDictionary.MergedDictionaries)
{
style = FindStyle(mergedDictionary, key);
if (style != null) break;
}
}
return style;
}
and is called with...
Style errorIcon = FindStyle(Application.Current.Resources, "AddNewIcon");

Application.Current.Resources.TryGetValue("key", out var result);
will also look in the merged dictionaries (as stated by #pinedax here).

I do encourage you to use the default way of fetching resources out of a ResourceDictionary as for me it is very efficient.
If you look at the ResourceDictionary source code (here) you will see there's an internal dictionary that holds all the resources keys, so when we do:
Application.Current.Resources["key"]
This is actually getting the value from that internal dictionary and dictionary lookup by key is very efficient.
But to answer your question, you should be able to get the values using Linq by using the SelectMany method on the MergeDictionaries property. Something like this:
var mergedDictionary = Application.Current.Resources.MergedDictionaries;
var resourceK = mergedDictionary.SelectMany(x => x)
.Where(v => v.Key == "Key")
.Select(t => t.Value)
.FirstOrDefault();
Hope this helps.-

Related

How can programmatically change from Light Mode to Dark Mode in Xamarin.Forms Android and / or programmatically change the color of the scroll bar?

I want to allow my user to select the AppTheme as he wants. Following are the options I want to give :-
Automatic (As per the user selection in Settings).
Light Mode
Dark Mode
I found a solution to use the below code:-
if(theme == App.Theme.Light)
{
Delegate.SetLocalNightMode(AppCompatDelegate.ModeNightNo);
} else
{
Delegate.SetLocalNightMode(AppCompatDelegate.ModeNightYes);
}
But this code recreates the entire activity and on the click on the button, I am again taken to the LoginPage of my App.
Can anyone suggest me a way to programmatically change from Light Mode to Dark Mode in Xamarin.Forms Android?
Also does anyone know another way that I could programmatically change the color of the scroll bar.
Have two style files Light and Dark XAML and ThemeHelper class which switches the themes at run time
Refer this https://github.com/jamesmontemagno/Hanselman.Forms/tree/vnext/src/Hanselman/Styles
Update
As mentioned this sample project has great resources for theme switching.
Light Theme
<?xml version="1.0" encoding="UTF-8" ?>
<ResourceDictionary
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestApp.LightTheme">
<Color
x:Key="TextColor">#ababab</Color>
<Color
x:Key="GenericBackground">#e3e3e3</Color>
<Color
x:Key="AppBackground">#FFFFFF</Color>
</ResourceDictionary>
Dark theme
<?xml version="1.0" encoding="UTF-8"?>
<ResourceDictionary
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestApp.DarkTheme">
<Color
x:Key="TextColor">#e3e3e3</Color>
<Color
x:Key="GenericBackground">#ababab</Color>
<Color
x:Key="AppBackground">#000000</Color>
</ResourceDictionary>
ThemeHelper
public class ThemeHelper
{
public static Theme CurrentTheme = Theme.Light;
public static void ChangeTheme(Theme theme, bool forceTheme = false)
{
// don't change to the same theme
if (theme == CurrentTheme && !forceTheme)
return;
//// clear all the resources
var applicationResourceDictionary = Application.Current.Resources;
ResourceDictionary newTheme;
if (theme == Theme.Default)
{
theme = AppInfo.RequestedTheme == AppTheme.Dark ? Theme.Dark : Theme.Light;
}
switch (theme)
{
case Theme.Light:
newTheme = new LightTheme();
break;
case Theme.Dark:
newTheme = new DarkTheme();
break;
case Theme.Default:
default:
newTheme = new LightTheme();
break;
}
ManuallyCopyThemes(newTheme, applicationResourceDictionary);
CurrentTheme = theme;
var background = (Color)App.Current.Resources["AppBackground"];
}
static void ManuallyCopyThemes(ResourceDictionary fromResource, ResourceDictionary toResource)
{
foreach (var item in fromResource.Keys)
{
toResource[item] = fromResource[item];
}
}
}
Theme enum
public enum Theme
{
Default,
Light,
Dark
}
Before starting the app initialize theme , you can retrieve it from Application settings or from DB like SQLite or API too
public App()
{
InitializeComponent();
ThemeHelper.ChangeTheme(Theme.Default);
MainPage = new NavigationPage(new MainPage());
}
Make sure you use DynamicResource in this approach
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
BackgroundColor="{DynamicResource AppBackground}"
x:Class="TestApp.MainPage">
<ContentPage.ToolbarItems>
<ToolbarItem
Text="Default"
Clicked="ToolbarItem_Clicked" />
<ToolbarItem
Text="Light"
Clicked="ToolbarItem_Clicked_1" />
<ToolbarItem
Text="Dark"
Clicked="ToolbarItem_Clicked_2" />
</ContentPage.ToolbarItems>
<StackLayout>
<Label
Text="Welcome to Xamarin.Forms!"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand"
TextColor="{DynamicResource TextColor}"
BackgroundColor="{DynamicResource GenericBackground}" />
<Label
x:Name="label"
TextColor="{DynamicResource TextColor}"
BackgroundColor="{DynamicResource GenericBackground}"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
Screenshot
You may still need some changes in Android with respect to different API levels.
Other reference
Theming
Dark mode detection
Android Dark Theme
You can use DynamicResources for implement dynamic theme functionality without redirection. You can follow these documents.

in visual studio 2019, how to customise navigation bar in each view (not globally)

I want to customise navigation bar for one view (again, not globally, just for that one view). I have search on the internet and only found how to customise it globally from the app.xaml file.
I have tried to code as below in my view.xaml file
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor"
Value="{StaticResource blackColor}"/>
<Setter Property="BarTextColor"
Value="{StaticResource whiteColor}"/>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
but it does not work unlike when I stylise other item such as entries/buttons
Regarding Style if Key is not set to a value, then style is applied to all the objects of the TargetType.
In your App.Xaml, set a Style with x:Key
<Style TargetType="ContentPage" x:Key="specialPage">
<Setter Property="BackgroundColor" Value="Green"/>
</Style>
In your ContentPage page
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
Style="{StaticResource Key=specialPage}"
As you can see this style is for ContentPage as NavigationApp is set only once for an Application you could set the property of NavigationBar in ContentPage Style.
So,
1) You can keep the NavigationPage static and use the static variable to change the NavigationPage Property
App.Xaml.cs
public static App CurrentApplication;
public NavigationPage AppNavigationPage;
public App()
{
InitializeComponent();
CurrentApplication = this;
this.AppNavigationPage = new NavigationPage(new MainPage());
MainPage = this.AppNavigationPage;
}
Page.Xaml.cs
public Page1()
{
InitializeComponent();
App.CurrentApplication.AppNavigationPage.BarBackgroundColor = Color.Red;
}
2) Use Dynamic resource for styling the NavigationPage
NavigationPage.TitleView
This tag will help you to customize your Navigation bar.

Why doesn't (Style)Application.Current.Resources["FrameBorder"] let me access a resource in my ResourceDirectory

I created a resource in the file FrameRes.xaml like this:
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Japanese.Resources.FrameRes">
<Style x:Key="FrameBorder" TargetType="Frame">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="HasShadow" Value="false" />
<Setter Property="Margin" Value="10,0" />
<Setter Property="BorderColor" Value="{DynamicResource LineColor}" />
<Setter Property="Padding" Value="0" />
<Setter Property="VerticalOptions" Value="Start" />
</Style>
</ResourceDictionary>
and this is contained inside:
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns:converters="clr-namespace:Japanese"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Japanese.App">
<Application.Resources>
<ResourceDictionary Source="/Resources/FooterRes.xaml" />
<ResourceDictionary Source="/Resources/FrameRes.xaml" />
</Application.Resources>
</Application>
When I try to access this in C# like this then it fails and says there's no reference to FrameBorder:
var fb2 = (Style)Application.Current.Resources["FrameBorder"];
When I try to access this in C# like this it works:
Application.Current.Resources.TryGetValue("FrameBorder", out object frameBorder);
var fb = (Style)frameBorder;
Does anyone know why the first way doesn't work. It looks the same to me.
The way you have it set up, you have a ResourceDictionary inside another ResourceDictionary with no key/name/reference. When you call Application.Current.Resources["FrameBorder"];, you are accessing the upper-most level of the dictionary and looking for "FrameBorder", not its sub-levels. However, calling TryGetValue goes through all levels of the Dictionary.
Read through the Docs to understand how to access values from Dictionaries.
Ok, so I have played around with the ResourceDictionary tags and files, and I can see where you're going wrong.
According to the Xamarin Forms Docs on ResourceDictionary, you can add stand-alone ResourceDictionary XAML files to your App.xaml like so:
<App ...>
<App.Resources>
<!-- Add more resources here -->
<ResourceDictionary Source="MyResourceDictionary.xaml" />
<!-- Add more resources here -->
</App.Resources>
...
</App>
You can replace App with ContentPage or ContentView, depending on your use.
What does this do?
Simply put, this creates a new ResourceDictionary object and merges other ResourceDictionary files into it. Let's go into a bit more detail.
Let's start by looking at the Application.Current.Resources property, which is of type ResourceDictionary and implements ICollection, IDictionary, and IResourceDictionary. Given the interfaces it implements, you can obtain the values by numeric or string ID (ex: App.Current.Resources[0] or App.Current.Resources["LabelStyle"]). This accesses the top-level contents of the dictionary, so only those that have been created in the <[App/ContentPage/ContentView].Resources> tag.
You also have a property called MergedDictionaries, which implements ICollection. This means that you can only access the list items by numeric ID (ex: MyMergedDictionary[0]).
When you add a ResourceDictionary file like:
<ResourceDictionary Source="MyResourceDictionary.xaml" />
You are actually merging this file with the current ResourceDictionary. To then access the contents of MyResourceDictionary you would call (in C#):
App.Current.Resources.MergedDictionaries[0]["InternalKeyName"]
This is probably better explained like so:
<App ...>
<App.Resources>
<!-- Called using App.Current.Resources["PrimaryColor"] -->
<Color x:Key="PrimaryColor">#2196F3</Color>
<!-- Called using App.Current.Resources.MergedDictionaries[0]["InternalKeyName"] -->
<ResourceDictionary Source="MyResourceDictionary.xaml" />
</App.Resources>
...
</App>
Your case
In your case you have two dictionaries merged into your Application Resources: FooterRes and FrameRes.
For better management, I would create an enum like:
public enum MergedResourcesEnum
{
Footer,
Frame
}
and use this enum when calling the resources in C# like so:
Application
.Current
.Resources
.MergedDictionaries[(int)MergedResourcesEnum.Frame]["FrameBorder"];
You can try to delete the outer ResourceDictionary tag and both of
var fb2 = (Style)Application.Current.Resources["FrameBorder"];
and
Application.Current.Resources.TryGetValue("FrameBorder", out object frameBorder);
var fb = (Style)frameBorder;
work right.
So your xaml code is like this:
(Please ignore the unrelated package name and FooterRes.xaml because I you didnot share your FooterRes.xaml file)
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App75.App">
<Application.Resources>
<ResourceDictionary Source="/Resources/FooterRes.xaml" />
</Application.Resources>
</Application>

How can I set ResourceDictionary FontSize on XAML?

Apologies if this would be a messy one. I'm pretty new to Xamarin. Currently I'm trying to convert this resource dictionary code to a XAML:
Current.Resources = new ResourceDictionary {
{ FontResources.DefaultButtonFontAttribute, FontAttributes.Bold },
{ FontResources.DefaultLabelFontSize,
Xamarin.Forms.Device.GetNamedSize(NamedSize.Small, typeof(Label)) },
{ StyleResources.DefaultLabelStyle, LABEL_STYLE }
}
(FontResources and StyleResources is a RESX file that contains the key name) Having
public static Style LABEL_STYLE = new Style(typeof(Label))
{
Setters = {
new Setter { Property = Label.FontSizeProperty, Value = new DynamicResource( FontResources.DefaultLabelFontSize )},
new Setter { Property = Label.FontAttributesProperty, Value = new DynamicResource( FontResources.DefaultLabelFontAttribute )},
}
};
The way I'm trying to do this is like this:
<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Styles.App">
<Application.Resources>
<ResourceDictionary>
<FontAttributes x:Key="DefaultLabelFontAttribute"></FontAttributes>
<FontSize x:Key="DefaultLabelFontSize"></FontSize>
<Style x:Key="DefaultLabelStyle" TargetType="Label">
<Setter Property="FontAttributes" Value="{DynamicResource DefaultLabelFontAttribute}"></Setter>
<Setter Property="FontSize" Value="{DynamicResource DefaultLabelFontSize}"></Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
But it seems that I'm not doing it the right way as there's no FontSize property similar to the FontAttribute. All I can see is FontSizeConverter. Also is there a way to call this code: Xamarin.Forms.Device.GetNamedSize(NamedSize.Small, typeof(Label)) to the Xaml?
Also is there a way to call this code: Xamarin.Forms.Device.GetNamedSize(NamedSize.Small, typeof(Label)) to the Xaml?
ResourceDictionary:
<ResourceDictionary>
<Style x:Key="Labelfont" TargetType="Label">
<Setter Property="FontSize" Value="Small" />
</Style>
</ResourceDictionary>
Style Usage:
Label Text="UsingSmallFont" Style="{StaticResource Labelfont}"/>

Apply style to control in custom renderer

I want to apply a style to a control. This is the style
<Application.Resources>
<ResourceDictionary>
<SolidColorBrush x:Key="BackgroundColor" Color="Yellow" />
<Style TargetType="Button" x:Name="myNewButtonStyle">
<Setter Property="Background" Value="{StaticResource BackgroundColor}" />
</Style>
</ResourceDictionary>
</Application.Resources>
, which can be found in App.xaml (UWP project). And here is the custom renderer:
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (this.Element != null)
{
var style = Windows.UI.Xaml.Application.Current.Resources["myNewButtonStyle"] as Windows.UI.Xaml.Style;
this.Control.Style = style;
}
}
The idea is based on this answer. But the style isn't applied. Setting the background color in code does work:
this.Control.BackgroundColor = new SolidColorBrush(Windows.UI.Colors.Yellow);
How can I apply a style to a control in a custom renderer?
I've filled a bug report. It should be fixed in one of the upcoming releases (> XF 2.3.3.166-pre4).

Resources