I am trying to create my own MarkupExtension for localization. The idea is to pass a name of a resource (for example 'Save') to the markup extension and the return would be localized value (for example 'Save' in en-US, 'Speichern' in de-de and so on).
This works pretty good, but I am unable to make it work with intellisense.
This is my simplified MarkupExtension class:
public class MyMarkupExtension : MarkupExtension
{
private readonly string _input;
public MyMarkupExtension(string input)
{
_input = input;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
// Here the actual value from the resources will be returned, for example for input 'Save':
// 'Save' for Thread.CurrentThread.CurrentUICulture="en-US"
// 'Speichern' for Thread.CurrentThread.CurrentUICulture="de-de"
// ...
return Resources.ResourceManager.GetString(_input);
}
}
And xaml:
<TextBox Text="{m:MyMarkup Save}"></TextBox> <!-- No Intellisense, but it works. -->
<TextBox Text="{m:MyMarkup {x:Static properties:Resources.Save}}"></TextBox> <!-- Intellisense works, but the input parameter for markup extension is already localized string -->
Any idea what to use in xaml so that the input to markup extension would be the literal string ('Save' in my example, which is a resource name, not a localized value) and that intellisense would work?
Markup extensions are a XAML technique for getting a value that is neither a primitive nor a specific XAML type. Generally, for attribute usage, markup extensions are identified by being covered by a start and end brace. For example: {x:Static local:AppConstants. DefaultName} .
The most common markup extensions used in WPF programming are those that support resource references ( StaticResource and DynamicResource ), and those that support data binding ( Binding ). StaticResource provides a value for a property by substituting the value of an already defined resource.
On the programmatic level, a XAML markup extension is a class that implements the IMarkupExtension or IMarkupExtension<T> interface. You can explore the source code of the standard markup extensions described below in the MarkupExtensions directory of the Xamarin. Forms GitHub repository.
XAML markup extensions help extend the power and flexibility of XAML by allowing element attributes to be set from sources other than literal text strings. In either case, the text string set to the Color attribute is converted to a Color value by the ColorTypeConverter class.
<TextBox Text="{m:MyMarkup Save}"></TextBox> <!-- No Intellisense, but it works. -->
About your frist one, there's not simple way(direct way) to make intellisense support a custom markup extension as a build-in one. If you need intellisense display resource name for you, you have to write a VS extension to do the search and provide the results for intellisense. In my opinion, this's not a easy thing to do. If you really like to try it, Walkthrough: Displaying Statement Completion could be your start.
<TextBox Text="{m:MyMarkup {x:Static properties:Resources.Save}}"></TextBox> <!-- Intellisense works, but the input parameter for markup extension is already localized string -->
About your second one, because StaticExtension provide the value hold by a static member, so you definitely got what was contain in Resources.Save which should be ResourceManager.GetString("Save", resourceCulture). Actually, the auto-generated code for Resources.Save is just like this.
internal static string Save {
get {
return ResourceManager.GetString("Save", resourceCulture);
}
}
The frist way to fix it, is write a ResourceDictionary that provide resource names.
<ResourceDictionary xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="Save">Save</sys:String>
</ResourceDictionary>
Then you can use it like this.
<TextBox Text="{m:MyMarkup {x:StaticResource Save}}">
You will definitely get intellisense support. Intellisense will search all resource keys that hold by a string type object for you.
And the second way is changing your markup extension's implement to handle the resource string directly. But it's depends on how you define your resource string and I can't give any further advice.
First, you can use special type instead of string, which will represent your resource key. That way you will make your extension type-safe (not allow to pass arbitrary strings there):
public class LocResourceKey {
// constructor is private
private LocResourceKey(string key) {
Key = key;
}
public string Key { get; }
// the only way to get an instance of this type is
// through below properties
public static readonly LocResourceKey Load = new LocResourceKey("Load");
public static readonly LocResourceKey Save = new LocResourceKey("Save");
}
public class MyMarkupExtension : MarkupExtension {
private readonly string _input;
public MyMarkupExtension(LocResourceKey input) {
_input = input.Key;
}
public override object ProvideValue(IServiceProvider serviceProvider) {
return Resources.ResourceManager.GetString(_input);
}
}
Now you might think that it's a lot of efforts to maintain such class with all resource keys from your resx file, and that's true. But you can use T4 template for it to be generated for you. For example:
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Windows.Forms" #>
<#@ output extension=".cs" #>
namespace WpfApplication1 {
public class LocResourceKey {
private LocResourceKey(string key) {
Key = key;
}
public string Key { get; }
<#using (var reader = new System.Resources.ResXResourceReader(this.Host.ResolvePath("Properties\\Resources.resx"))) {
var enumerator = reader.GetEnumerator();
while (enumerator.MoveNext()) {
Write("\r\n\t\t");
#>public static readonly LocResourceKey <#= enumerator.Key #> = new LocResourceKey("<#= enumerator.Key #>");<#
}
Write("\r\n");
}#>
}
}
This template assumes there is "Resources.resx" file under "Properties" folder relative to the template itself (template can be created via Add > New Item > Text Template). When run - it will inspect all resources in resx file and generate LocResourceKey
class for you.
After all that, you can use your keys in type-safe way, with the help of intellisense and visible errors if you typed something wrong:
<TextBlock Text="{my:MyMarkup {x:Static my:LocResourceKey.Save}}" />
<TextBox Text="{m:MyMarkup Save}"></TextBox> <!-- No Intellisense, but it works. --> <TextBox Text="{m:MyMarkup {x:Static properties:Resources.Save}}"></TextBox> <!-- Intellisense works, but the input parameter for markup extension is already localized string -->
Is there something wrong with simply replacing {m:MyMarkup Save}
with {x:Static p:Resources.Save}
? This should be equivalent, and it will give you IntelliSense support right out of the box.
The only difference I can see, aside from a bit of added verbosity, is that you call GetString(name)
instead of GetString(name, resourceCulture)
, but resourceCulture
is null by default, so there should be no difference.
Note that some shops, including Microsoft, use the abbreviated name SR
(short for "string resource[s]") in lieu of Resources
, so you could take a page from their book and shorten the markup a bit:
<TextBox Text="{x:Static p:SR.Save}" />
There's just one thing you'll need to do first, which is switch your resource file's Custom Tool over to PublicResXFileCodeGenerator
in the Properties pane. That will ensure the resource class and properties are given public visibility rather than internal, which you will need for x:Static
to work.
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