【问题标题】:MarkupExtensions, Constructor and IntellisenseMarkupExtensions、构造函数和智能感知
【发布时间】:2018-01-17 08:19:08
【问题描述】:

我正在尝试创建自己的 MarkupExtension 进行本地化。想法是将资源的名称(例如“Save”)传递给标记扩展,然后返回本地化值(例如 en-US 中的“Save”,de-de 中的“Speichern”等) .

这很好用,但我无法让它与智能感知一起工作。

这是我简化的 MarkupExtension 类:

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);
    }
}

还有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 -->

知道在 xaml 中使用什么,以便标记扩展的输入是文字字符串(在我的示例中为“Save”,它是资源名称,而不是本地化值)并且智能感知可以工作?

【问题讨论】:

  • 你说的“让它工作”是什么意思如何它现在不起作用?
  • @grek40:“让智能感知工作” - 现在我必须知道资源的名称,例如“保存”。我也可以输入错误并写'Svae'并且不会得到任何编译器错误。但是,如果您使用 x:Static 为例,那么我可以编写 'properties:' 并且智能感知会自动提供所有可能的值,包括 'Save'。
  • 最简单的解决方案当然是简单地设置Text="{x:Static properties:Resources.Save}"。这可以通过一些战略命名来缩短一点,但自定义标记扩展似乎完全没有必要。有关详细信息,请参阅我的答案。此外,无论您喜欢哪种解决方案,您都应该在奖励到期之前奖励某人
  • 如果您想要实现本地化,那么为什么不使用已经发明的东西:docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/…?

标签: c# .net wpf xaml markup-extensions


【解决方案1】:

首先,您可以使用特殊类型而不是字符串,这将代表您的资源键。这样你就可以让你的扩展类型安全(不允许在那里传递任意字符串):

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);
    }
}

现在您可能认为使用 resx 文件中的所有资源键来维护此类需要付出很多努力,这是真的。但是您可以使用 T4 模板为您生成它。例如:

<#@ 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");
        }#>
    }
}

此模板假定“Properties”文件夹下相对于模板本身存在“Resources.resx”文件(可以通过“添加”>“新建项目”>“文本模板”创建模板)。运行时 - 它将检查 resx 文件中的所有资源并为您生成 LocResourceKey 类。

毕竟,如果输入错误,您可以借助智能感知和可见错误以类型安全的方式使用您的密钥:

<TextBlock Text="{my:MyMarkup {x:Static my:LocResourceKey.Save}}" />

【讨论】:

    【解决方案2】:
    <TextBox Text="{m:MyMarkup Save}"></TextBox> <!-- No Intellisense, but it works. -->
    

    关于您的第一个,没有简单的方法(直接方法)使智能感知支持自定义标记扩展作为内置扩展。如果您需要智能感知显示资源名称,您必须编写一个 VS 扩展来进行搜索并为智能感知提供结果。在我看来,这不是一件容易的事。如果您真的想尝试,Walkthrough: Displaying Statement Completion 可能是您的开始。

    <TextBox Text="{m:MyMarkup {x:Static properties:Resources.Save}}"></TextBox> <!-- Intellisense works, but the input parameter for markup extension is already localized string -->
    

    关于你的第二个,因为 StaticExtension 提供静态成员持有的值,所以你肯定得到了 Resources.Save 中包含的内容,应该是 ResourceManager.GetString("Save", resourceCulture)。其实Resources.Save的自动生成代码就是这样的。

    internal static string Save {
        get {
            return ResourceManager.GetString("Save", resourceCulture);
        }
    }
    

    修复它的第一种方法是编写一个提供资源名称的 ResourceDictionary。

    <ResourceDictionary xmlns:sys="clr-namespace:System;assembly=mscorlib">
        <sys:String x:Key="Save">Save</sys:String>
    </ResourceDictionary>
    

    那你就可以这样使用了。

    <TextBox Text="{m:MyMarkup {x:StaticResource Save}}">
    

    您一定会获得智能感知支持。 Intellisense 将为您搜索字符串类型对象持有的所有资源键。

    第二种方法是更改​​标记扩展的实现以直接处理资源字符串。但这取决于您如何定义资源字符串,我无法提供任何进一步的建议。

    【讨论】:

      【解决方案3】:
      <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 -->
      

      简单地将{m:MyMarkup Save} 替换为{x:Static p:Resources.Save} 有什么问题吗?这应该是等效的,它会立即为您提供 IntelliSense 支持。

      除了有点冗长之外,我能看到的唯一区别是您调用 GetString(name) 而不是 GetString(name, resourceCulture),但 resourceCulture 默认为 null,因此应该没有区别。

      请注意,包括 Microsoft 在内的一些商店使用缩写名称 SR(“字符串资源 [s]”的缩写)代替 Resources,因此您可以从他们的书中获取一页并缩短标记 a位:

      <TextBox Text="{x:Static p:SR.Save}" />
      

      您首先需要做一件事,即将资源文件的自定义工具切换到属性窗格中的PublicResXFileCodeGenerator。这将确保资源类和属性被赋予公共可见性而不是内部可见性,x:Static 需要它才能工作。

      【讨论】:

        猜你喜欢
        • 2017-08-03
        • 1970-01-01
        • 2010-10-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-27
        • 2010-11-24
        • 1970-01-01
        相关资源
        最近更新 更多