WPF本地化:使用StringFormat的DynamicResource?

9

我正在使用ResourceDictionary进行.NET 4本地化。有没有人有一个解决方案,可以使用带有字符串格式的值?

例如,假设我有一个键为"SomeKey"的值:

<ResourceDictionary ...>
    <s:String x:Key="SomeKey">You ran {0} miles</s:String>
</ResourceDictionary>

在TextBlock中使用它:

<TextBlock Text="{DynamicResource SomeKey}" />

例如,如何将一个整数与SomeKey的值组合成格式字符串?


在 ResourceDictionary 中进行本地化而不是(更常见的)使用 resx 资源的强有力的原因是什么? - H H
到目前为止,我采用了完整的XAML方法。我也不确定是否可以使用resx在运行时更改语言。目前,我正在使用外部.xaml文件,我可以轻松修改和加载这些文件,以便在需要更改语言时进行操作(除上述情况外)。 - Andy Clark
3个回答

3
你需要以某种方式绑定到 ViewModel.Value,然后使用一个(嵌套的)绑定格式字符串。
当你只有一个值时:
<TextBlock 
  Text="{Binding Path=DemoValue, StringFormat={StaticResource SomeKey}}" />        

当你还有{1}等内容时,你需要使用MultiBinding。

编辑:

如果你真的想在实时表单中更改语言,那么明智的方法可能是在ViewModel中完成所有格式设置。我很少在MVVM中使用StringFormat或MultiBinding。


2
你说得对,DynamicResource 在这里行不通。如果你真的想在一个活动窗体中更改语言,那就是个问题。大多数UI更改器只会重新加载窗体。为了实现这个功能而在整个应用程序中使用动态资源有点昂贵。 - H H
1
是的,我开始意识到了。我可以在我的视图模型层添加某种翻译服务来处理它。我仍在学习MVVM模式,到目前为止,我发现在视图中处理所有翻译最容易。 - Andy Clark
2
同意。在VM中完成更多的工作,可以更轻松地进行单元测试和处理更复杂的格式化/多绑定。 - KornMuffin

2
所以,我终于想出了一个解决方案,允许我在ResourceDictionary中拥有格式字符串,并能够动态地在运行时更改语言。我认为它可以改进,但它是有效的。
这个类将资源键转换为来自ResourceDictionary的值:
public class Localization
{
    public static object GetResource(DependencyObject obj)
    {
        return (object)obj.GetValue(ResourceProperty);
    }

    public static void SetResource(DependencyObject obj, object value)
    {
        obj.SetValue(ResourceProperty, value);
    }

    // Using a DependencyProperty as the backing store for Resource.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ResourceProperty =
        DependencyProperty.RegisterAttached("Resource", typeof(object), typeof(Localization), new PropertyMetadata(null, OnResourceChanged));

    private static void OnResourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        //check if ResourceReferenceExpression is already registered
        if (d.ReadLocalValue(ResourceProperty).GetType().Name == "ResourceReferenceExpression")
            return;

        var fe = d as FrameworkElement;
        if (fe == null)
            return;

        //register ResourceReferenceExpression - what DynamicResourceExtension outputs in ProvideValue
        fe.SetResourceReference(ResourceProperty, e.NewValue);
    }
}

这个类允许从ResourceDictionary中获取的值被用作String.Format()中的格式参数。

public class FormatStringConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values[0] == DependencyProperty.UnsetValue || values[0] == null)
            return String.Empty;

        var format = (string)values[0];
        var args = values.Where((o, i) => { return i != 0; }).ToArray();

        return String.Format(format, args);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

示例用法 1: 在这个示例中,我使用FormatStringConverter将其绑定集合转换为所需的输出。例如,如果“SomeKey”的值为“The object id is {0}”,而“Id”的值为“1”,那么输出将变成“The object id is 1”。

                <TextBlock ap:Localization.Resource="SomeKey">
                    <TextBlock.Text>
                        <MultiBinding Converter="{StaticResource formatStringConverter}">
                            <Binding Path="(ap:Localization.Resource)" RelativeSource="{RelativeSource Self}" />
                            <Binding Path="Id" />
                        </MultiBinding>
                    </TextBlock.Text>
                </TextBlock>

示例用法2:在这个示例中,我使用绑定和转换器来将资源键更改为更详细的内容,以防止键冲突。例如,如果我有枚举值Enum.Value(默认显示为“Value”),我使用转换器将其命名空间附加到更具唯一性的键上。因此,该值变为“My.Enums.Namespace.Enum.Value”。然后,Text属性将根据ResourceDictionary中“My.Enums.Namespace.Enum.Value”的值进行解析。

        <ComboBox ItemsSource="{Binding Enums}"
                  SelectedItem="{Binding SelectedEnum}">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock ap:Localization.Resource="{Binding Converter={StaticResource enumToResourceKeyConverter}}"
                               Text="{Binding Path=ap:Localization.Resource), RelativeSource={RelativeSource Self}}"/>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>

示例用法3:在此示例中,键是文字字面量,仅用于查找其在ResourceDictionary中对应的值。例如,如果“SomeKey”具有值“SomeValue”,则它将简单地输出“SomeValue”。

                    <TextBlock ap:Localization.Resource="SomeKey"
                               Text="{Binding Path=ap:Localization.Resource), RelativeSource={RelativeSource Self}}"/>

1
如果您想将Miles属性绑定和格式化到'TextBlock',可以按照以下方式进行:
<TextBlock Text="{Binding Miles, StringFormat={StaticResource SomeKey}}"/>

它必须是DynamicResource,因为它是从Application.Current.Resources.MergeDictionaries中的ResourceDictionary加载的。 - Andy Clark
嗯,这里静态/动态没什么区别。而且MergedDictionaries并不自动需要动态。 - H H
DynamicResource 只能在 DependenyObject 的 DependencyProperty 上设置。在我的情况下,DynamicResource 是必须的,因为我希望在运行时加载资源。 - Andy Clark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接