Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MarkupExtensions, Constructor and Intellisense

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?

like image 253
sventevit Avatar asked Jan 17 '18 08:01

sventevit


People also ask

What is a markup extension?

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} .

What is markup extension in WPF?

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.

What is markup extension in xamarin forms?

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.

What is the advantage of using XAML markup extensions?

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.


3 Answers

<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.

like image 88
Alex.Wei Avatar answered Nov 12 '22 18:11

Alex.Wei


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}}" />
like image 24
Evk Avatar answered Nov 12 '22 17:11

Evk


<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.

like image 1
Mike Strobel Avatar answered Nov 12 '22 19:11

Mike Strobel