proper way of binding a ResourceDictionaries Source property - c#

The problem I have is the following, I want to be able to have localization in my silverlight app without the usage of the usual resource file (since that approach did not work for me).
Any way I have several dictionaries for each language that I support, basically in the following:
Localization.xaml --> for the default language english
Localization.de.xaml --> for german
Localization.fr.xaml --> for french, I think you get the idea now
.
.
.
now when define the merged dictionaries in my app.xaml I would need to be able the dynamically define which Localization.xaml to use depending on the current culture.
I know I could do something like this in the code behind of the app.xaml:
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new MainPage();
try
{ ResourceDictionary dict = new ResourceDictionary()
{
Source = new Uri("/assembly;component/.../Localizations." + Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName + ".xaml", UriKind.Relative)
};
Resources.MergedDictionaries.RemoveAt(0);
Resources.MergedDictionaries.Add(dict);
}
catch(Exception){}
}
There are two concern that prevent me form using it however.
First is that it is in the code behind, since I am using MVVM I would like to avoid doing that.
Second is that I have to foumble with the MergedDictionaries, the 0-th dictionary is basically the default Localization.xaml. So if I were to change the position of that dictionary, I have to ensure that I also edit it in the code behind, which leaves room for errors.
So what I did was to write a SourceProvider, which give ne the Uri based on the current culture.
the code looks like this:
public class SourceProvider
{
public static Uri LocalizationSource
{
get
{
Uri source = new Uri("assembly;component/.../Localization." + Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName + ".xaml", UriKind.Relative);
try
{
ResourceDictionary dict = new ResourceDictionary(){Source = source};
}
catch(Exception)
{
source = new Uri("/assembly;component/.../Localization.xaml", UriKind.Relative);
}
return source;
}
}
}
Now in the App.xaml I can uses this as follows:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ns="clr-namespace:namespace of the sourceprovider"
x:Class="Peripherie.Configurator.App">
<Application.Resources>
<ResourceDictionary>
<ns:SourceProvider x:Key="Sourcer"/>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="{Binding LocalizationSource, Source={StaticResource Sourcer}}"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
This all works fine and I can bascially use the correct source, however rhis will give me the "Value does not fall within the expected range" error, which is kinda annoying.
Also all StaticResources used are not found because of the error as well, so it would show error which are not actually there, potentially flodding me with thousands of error for missing resources
Is there a way to prevent that ?

Related

Refresh all String Resources when Culture Changed

I've written a WPF app in which user can change culture. The Method in app.xaml.cs looks like this :
public void SelectCulture(string culture)
{
// List all our resources
List<ResourceDictionary> dictionaryList = new List<ResourceDictionary>();
foreach (ResourceDictionary dictionary in Application.Current.Resources.MergedDictionaries)
{
dictionaryList.Add(dictionary);
}
// We want our specific culture
string requestedCulture = string.Format("Resources/StringResources.{0}.xaml", culture);
ResourceDictionary resourceDictionary = dictionaryList.FirstOrDefault(d => d.Source.OriginalString == requestedCulture);
if (resourceDictionary == null)
{
// If not found, we select our default language
//
requestedCulture = "Resources/StringResources.en-ES.xaml";
resourceDictionary = dictionaryList.FirstOrDefault(d => d.Source.OriginalString == requestedCulture);
}
// If we have the requested resource, remove it from the list and place at the end.\
// Then this language will be our string table to use.
if (resourceDictionary != null)
{
Application.Current.Resources.MergedDictionaries.Remove(resourceDictionary);
Application.Current.Resources.MergedDictionaries.Add(resourceDictionary);
}
// Inform the threads of the new culture
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(culture);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
}
This allows to change on runtime every string resources from different resourcedictionnary.
But not every resources: those which are used in xaml via DynamicResource like this :
<GroupBox Header="{DynamicResource RootSettings}" >
are correctly updated.
But those which are used in C# code like in ViewModel like this :
stTestConnection = System.Windows.Application.Current.Resources["Connectionsucceded"].ToString();
do not update.
All string Resources are declared in different resources dictionaries like this :
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib">
<!-- #region Global -->
<system:String x:Key="CriticalErrorTitle">Critical Error</system:String>
<system:String x:Key="ErrorTitle">Error</system:String>
<system:String x:Key="CloseApp">Application will close</system:String>
Any idea why the string resources used in C# are not updating but the ones used in XAML are ?
Ask me if more info needed.
Thanks
I had to do something similar recently with a runtime theme change performing a colour change using Xamarin in c#.
If I remember correctly then in Xaml with DynaminResource they updated automatically. But when in came to the c# code I had to do so manually. This was triggered by a xamarin OnThemeChanged.
If you have access to a similar method during culture change, you could put all your update logic there.

WPF - Change theme from dark to light by clicking proper button - Avalonia

I would like to give the user an option to change the theme from dark to light, by clicking the button.
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp"
x:Class="CoreBackup.App">
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseDark.xaml"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
</Application.Styles>
How I can get access from other ViewModel to the Application.Styles?
I don't know if the problem is solved. So may be this helps to switch the theme from BaseDark to BaseLight.
private void ChangeLayoutExecute(object o)
{
// create new style
var newStyle = new StyleInclude(new Uri("avares://AvaloniaApplicationTest/App.xaml"));
newStyle.Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml");
// load style to get access to the ressources
var baseDarkStyle = newStyle.Loaded as Style;
// get the original source (BaseDark)
var ressourceFromAppXaml = ((Style)((StyleInclude)Application.Current.Styles[1]).Loaded).Resources;
foreach (var item in baseDarkStyle.Resources)
{
// for secure lookup if the key exists for the resource otherwise create it
if (ressourceFromAppXaml.ContainsKey(item.Key))
ressourceFromAppXaml[item.Key] = item.Value;
else
ressourceFromAppXaml.Add(item.Key, item.Value);
}
// set source name for the new theme
((StyleInclude)Application.Current.Styles[1]).Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml");
}

Drawing onto ESRI.ArcGIS.Client.FeatureLayer with a specific color

I am trying to build a feature that lets the user draw onto specific features layers with a specific color. By changing FeatureLayer.Renderer all annotations on the layer change to the specified color, even the annotations that were there from a previous session. I want to be able to have the old annotations there with their specific color and new ones be drawn with their specific color (potentially different).
Here is the XAML where the map and feature layer is defined
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="GraphicsDictionary.xaml" x:Name="LineSymbolResourceDictionary"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid Name="MapGrid">
<esri:Map x:Name="MyMap">
<esri:ArcGISTiledMapServiceLayer
ID="StreetMapLayer"
x:Name="BaseMap"
Url="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"
>
</esri:ArcGISTiledMapServiceLayer>
<esri:FeatureLayer
ID="MyFeatureLayer"
x:Name="MyFeatureLayer"
Url="http://123.123.123.12:6080/arcgis/rest/services/Prj/FeatureServer/0"
Renderer="{StaticResource BlueSimpleRenderer}"
EndSaveEdits="drawLayer_EndSaveEdits"
>
</esri:FeatureLayer>
</esri:Map>
</Grid>
The resource dictionary:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:esriSymbols="clr-namespace:ESRI.ArcGIS.Client.Symbols;assembly=ESRI.ArcGIS.Client"
xmlns:esri="clr-namespace:ESRI.ArcGIS.Client;assembly=ESRI.ArcGIS.Client"
x:Class="ClassName.GraphicsDictionary"
>
<esri:SimpleRenderer x:Key="BlueSimpleRenderer">
<esri:SimpleRenderer.Symbol>
<esriSymbols:SimpleLineSymbol x:Name="BlueLineSymbol" Color="#00007F" Width="5"/>
</esri:SimpleRenderer.Symbol>
</esri:SimpleRenderer>
The C# Code for drawing:
public void StartDrawing(FeatureLayer inputLayer, string inputColorInHex)
{
MyMap.Cursor = System.Windows.Input.Cursors.Pen;
//Below's the color that might be different from the original renderer
SimpleRenderer newRend= new SimpleRenderer
{
Symbol = new SimpleLineSymbol((Color)ColorConverter.ConvertFromString(inputColorInHex), 12)
};
inputLayer.Renderer = newRend as IRenderer;
MyDrawObject.DrawMode = ESRI.ArcGIS.Client.DrawMode.Freehand;
MyDrawObject.IsEnabled = true;
}
The renderer applies to all features (graphics) in the layer so if you want to use a renderer you could use a unique value renderer and set different styles for different attribute values though this implies that you know what values to use when defining the colours. Similarly if you want to control the colours based on a range of values then you can use a class breaks renderer.
In your situation though you may just want to set specific symbols for features since a symbol will override the renderer. You just apply the symbol to the graphic in order for it to be used. e.g.
var markerSym = new Esri.ArcGISRuntime.Symbology.SimpleMarkerSymbol
{
Style = Esri.ArcGISRuntime.Symbology.SimpleMarkerStyle.Diamond,
Color = Colors.Green,
Size = 18
};
var pointGraphic = new Esri.ArcGISRuntime.Layers.Graphic(point, markerSym);
graphicsLayer.Graphics.Add(pointGraphic);

How to update strings that are generated by a converter when I change language?

I have a WPF application in which I support multiple langauges. I can change the language runtime, as I load different sets of resource dictionaries containing the string resources upon change of language.
private void UpdateLoadedLanguages(object sender, SelectionChangedEventArgs e)
{
RemoveLoadedLanguages();
ResourceDictionary aResourceDictionary = new ResourceDictionary();
ResourceDictionary bResourceDictionary = new ResourceDictionary();
ResourceDictionary cLanguageResourceDictionary = new ResourceDictionary();
Language selectedLanguage = (Language) e.AddedItems[0];
switch (selectedLanguage)
{
case Language.enUS:
a.Source = new Uri("pack://application:,,,/[Projecta];component/src/Helpers/Language.en-US.xaml");
b.Source = new Uri("pack://application:,,,/[Projectb];component/src/resources/Language.en-US.xaml");
c.Source = new Uri("pack://application:,,,/[Projectc];component/src/resources/Language.en-US.xaml");
break;
case Language.nlNL:
a.Source = new Uri("pack://application:,,,/[Projecta];component/src/Helpers/Language.nl-NL.xaml");
b.Source = new Uri("pack://application:,,,/[Projectb];component/src/resources/Language.nl-NL.xaml");
c.Source = new Uri("pack://application:,,,/[Projectc];component/src/resources/Language.nl-NL.xaml");
break;
default:
a.Source = new Uri("pack://application:,,,/[Projecta];component/src/Helpers/Language.en-US.xaml");
b.Source = new Uri("pack://application:,,,/[Projectb];component/src/resources/Language.en-US.xaml");
c.Source = new Uri("pack://application:,,,/[Projectc];component/src/resources/Language.en-US.xaml");
break;
}
App.Current.Resources.MergedDictionaries.Add(aResourceDictionary );
App.Current.Resources.MergedDictionaries.Add(bResourceDictionary );
App.Current.Resources.MergedDictionaries.Add(cResourceDictionary );
}
A resource dictionary with the translations typically looks like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<system:String x:Key="DefaultApplicationTitleString">Title</system:String>
<system:String x:Key="DefaultDoneString">Done</system:String>
<system:String x:Key="DefaultCancelString">Cancel</system:String>
<system:String x:Key="DefaultDescriptionString">Description unknown</system:String>
<system:String x:Key="LanguageSelectionString">Language</system:String>
<system:String x:Key="AisleString">Aisle</system:String>
<system:String x:Key="LevelsString">levels</system:String>
</ResourceDictionary>
When I use one of the strings as defined above like directly below it works perfect.
Title="{DynamicResource DefaultApplicationTitleString}"
But with a converter like this ...
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = (string) Application.Current.FindResource("DefaultDescriptionString");
if (value != null)
{
string temp = (string) value;
char[] stringSeparators = { 'A', '.', '-' };
string[] stringElements= temp.Split(stringSeparators, StringSplitOptions.RemoveEmptyEntries);
string aisleString = (string) Application.Current.FindResource("AisleString");
string levelsString = (string) Application.Current.FindResource("LevelsString");
text = aisleString + " " + stringElements[1] + ", " + levelsString + " " + stringElements[2] + " - " +
stringElements[3];
}
return text;
}
... that I use like this ...
<TextBlock Text="{Binding Id, Converter={StaticResource ItemIdToTextConverter}}" />
... then my strings aren't updated when I change the language. If the property that I convert is updated on its viewModel then the language change does happen.
So my question: Does anyone have a solution for me where also my strings as a result of converters are updated on a language change?
My guesses for a solution are:
force an update of all frameworkelements that use a resource (How would I do this?)
or maybe do something with DynamicResourceExtenstion (I found this question: Return a dynamic resource from a converter, however I do not understand the explanation of the DynamicResourceExtension. So is this the way to go? If so, how would I implement this?)
This would be too much for a comment, but to little for an answer (or maybe not).
Converter is called only once when element is initializing. After that there is no link to what was the source if you are using StaticResource. If you use DynamicResource, then anytime when source get changed converter will be called again.
Do you change source in your localization approach? Nope.
Does converter gets invoked when language is changed? Yes, but only on notification from Id (PropertyChanged).
One way you can fix it is to subscribe your ViewModel to language switch event and call property changed event for each property. This will execute your converter despite source values still the same.
Other possibility would be to register somehow objects which required localized data. You will not use converter anymore, but an attached property (or behavior, depends on complexity). Enumerating (or keeping a list) over elements with such property and updating it shouldn't be hard.
Third way (which I am going to use in my very first localized wpf application) is reflection. Register all windows upong creation (un-register when unloading of course), define a schema of how you identify localizable properties of elements and go through it - win.
Regarding schema. My "localizable" controls with static content will be using already existing Uid to define a key, instead of defining things in separate resource dictionary it will be automatically generated). Dynamic ones I don't yet decide, perhaps there will be a behavior or attached property or even small peace of code behind (I like MVVM, but I am not making it an absolute) to reassign values from static class with predefined strings, which will be changed by reflection.

Object to add resources to ResourceDictionary

I'd like to add several resources to a ResourceDictionary from within one single line of XAML. I'll try to make an example out of it.
Imagine this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:util="clr-namespace:Util">
<util:MyResourceDictionaryA x:Key="MyResourceDictionaryA" />
<util:MyResourceDictionaryB x:Key="MyResourceDictionaryB" />
<util:MyResourceDictionaryC x:Key="MyResourceDictionaryC" />
<util:MyResourceDictionaryD x:Key="MyResourceDictionaryD" />
</ResourceDictionary>
I'd like to have something like:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:util="clr-namespace:Util">
<util:AllDictionaries x:Key="AllDictionaries" BaseName="MyResourceDictionary" />
</ResourceDictionary>
I've tried making a new ResourceDictionary like this:
public class AllDictionaries : ResourceDictionary
{
public string BaseName { get; set; }
public AllDictionaries ()
{
Clear();
var s = BaseName;
var DictionariesToLoad = new[] { "A", "B", "C", "D" };
foreach (var b in DictionariesToLoad )
{
var t = Type.GetType(b + s);
var resDir = Activator.CreateInstance(t) as ResourceDictionary;
Add(b + s, resDir);
}
}
}
However the constructor never gets called (or, not before something else in XAML references {StaticResource MyResourceDictionaryX} which hasn't been added to the application resources so it throws an exception). I also tried putting the code on the BaseName setter, to no avail.
This is an oversimplication of what I need, but I'm just trying to make the XAML more generic.
Any hints on how could one achieve this?
Note 1: the generated resource dictionaries need to be available (using their Key) resources for the rest of the XAML in the scope where I have created the AllDictionaries resource.
Note 2: for practical purposes, let's say I don't have access to MyResourceDictionaryX classes source code so I can't change anything on those
Merged Resource Dictionary already does what you want... it can be created through XAML or code.

Categories

Resources