使用DataTemplateSelector

时间:2018-12-10 01:05:20

标签: c# wpf enums multilingual datatemplateselector

我正在研究其中各个部分进行了充分讨论的东西,但是我很难将它们放在一起。我们有一个应用程序,其中包含许多需要不同输入参数的插件,而我正尝试将其设为多语言。我一直在研究动态GUI,该GUI检查插件以创建输入参数数组,并使用DataTemplateSelector根据参数的类型选择正确的控件。对于枚举器,我们正在尝试将本地化的显示名称绑定到组合框。关于如何进行枚举/组合框绑定,StackOverflow上有很多线程,但是我发现没有一个线程是多语言的和动态的(数据模板或其他)。

布莱恩·拉古纳斯(Brian Lagunas)的一篇很棒的博客文章几乎将我们带到了http://brianlagunas.com/localize-enum-descriptions-in-wpf。但是,他在XAML中静态绑定了枚举。我们有数百个枚举,并且一直在创建新的枚举。因此,我正在努力使自己更好地实现更具动态性的东西。沿着这条线的某个地方,我需要使用反射来找出枚举器的类型并将其绑定到组合框,但是我无法完全确定在哪里,何时或如何。

我在此处上传了扩展示例:https://github.com/bryandam/Combo_Enum_MultiLingual。我将在此处尝试包括相关的内容,但是很难将其压缩下来。

public partial class MainWindow : Window
{
    public ObservableCollection<Object> InputParameterList { get; set; } = new ObservableCollection<Object>();
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;

        //Create an example input object.
        InputParameter bitlocker_drive = new InputParameter();
        bitlocker_drive.Name = "BitLocker Enabled";
        bitlocker_drive.Type = typeof(String);
        InputParameterList.Add(bitlocker_drive);

        InputParameter bitlocker_status = new InputParameter();
        bitlocker_status.Name = "Status";
        bitlocker_status.Type = typeof(Status);
        InputParameterList.Add(bitlocker_status);

        InputParameter bitlocker_foo = new InputParameter();
        bitlocker_foo.Name = "Foo";
        bitlocker_foo.Type = typeof(Foo);
        InputParameterList.Add(bitlocker_foo);
    }
}

这是我的XAML:

<Window x:Class="BindingEnums.MainWindow"
  ....
<Window.Resources>        
    ...
    <DataTemplate x:Key="ComboBox">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <TextBlock Text="{Binding Name, Mode=TwoWay}" />
            <ComboBox ItemsSource="{Binding Source={local:EnumBindingSource {x:Type local:Status}}}" Grid.Column="1"/>                
        </Grid>
    </DataTemplate>
    ...
    <local:InputParameterTemplateSelector x:Key="InputDataTemplateSelector" Checkbox="{StaticResource Checkbox}" ComboBox="{StaticResource ComboBox}" DatePicker="{StaticResource DatePicker}" TextBox="{StaticResource TextBox}"/>
</Window.Resources>
<Grid>
    <ListBox Name="InputParameters" KeyboardNavigation.TabNavigation="Continue" HorizontalContentAlignment="Stretch" ItemsSource="{Binding InputParameterList}"  ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" Background="Transparent" BorderBrush="Transparent" ItemTemplateSelector="{StaticResource InputDataTemplateSelector}">
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="IsTabStop" Value="False" />
                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
</Grid>

以下是我正在测试的两个示例枚举:

[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum Status
{        
    [Display(Name = nameof(Resources.EnumResources.Good), ResourceType = typeof(Resources.EnumResources))]
    Good,
    [Display(Name = nameof(Resources.EnumResources.Better), ResourceType = typeof(Resources.EnumResources))]
    Better,
    Best
}

[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum Foo
{
    [Display(Name = nameof(Resources.EnumResources.Foo), ResourceType = typeof(Resources.EnumResources))]
    Foo,
    [Display(Name = nameof(Resources.EnumResources.Bar), ResourceType = typeof(Resources.EnumResources))]
    Bar
}

这是枚举类型转换器:

    public class EnumDescriptionTypeConverter : EnumConverter
{
    public EnumDescriptionTypeConverter(Type type)
        : base(type)
    {}

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            if (value != null)
            {
                FieldInfo fi = value.GetType().GetField(value.ToString());
                if (fi != null)
                {
                    //Reflect into the value's type to get the display attributes.
                    FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
                    DisplayAttribute displayAttribute = fieldInfo?
                                                    .GetCustomAttributes(false)
                                                    .OfType<DisplayAttribute>()
                                                    .SingleOrDefault();
                    if (displayAttribute == null)
                    {
                        return value.ToString();
                    }
                    else
                    {
                        //Look up the localized string.
                        ResourceManager resourceManager = new ResourceManager(displayAttribute.ResourceType);                            
                        string name = resourceManager.GetString(displayAttribute.Name);
                        return string.IsNullOrWhiteSpace(name) ? displayAttribute.Name : name;
                    }
                }
            }

            return string.Empty;
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }

这是枚举绑定源标记扩展:

public class EnumBindingSourceExtension : MarkupExtension
{
    ...

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (null == this._enumType)
            throw new InvalidOperationException("The EnumType must be specified.");

        Type actualEnumType = Nullable.GetUnderlyingType(this._enumType) ?? this._enumType;
        Array enumValues = Enum.GetValues(actualEnumType);

        if (actualEnumType == this._enumType)
            return enumValues;

        Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1);
        enumValues.CopyTo(tempArray, 1);
        return tempArray;
    }
}

同样,我的目标是弄清楚如何避免静态绑定到单个枚举类型(如下面的XAML),而是根据输入参数可能是哪种类型来绑定它:

<ComboBox ItemsSource="{Binding Source={local:EnumBindingSource {x:Type local:Status}}}" Grid.Column="1"/

我在Windows后台代码,数据模板选择器甚至是自定义控件中都尝试过,但并没有取得太大的成功。我的第一个“真实” WPF应用程序使我很不自在地将所有这些与它们的各个部分放在一起。

Here's the example running

2 个答案:

答案 0 :(得分:0)

对此的一种可能的“解决方案”是完全删除枚举对本地化的使用。这就是我的工作,它使我自己和项目中的其他开发人员可以像这样将纯英文插入我们的XAML中:

<TextBlock Text="{Translate 'Scanning Passport'}" />

我写了一个小实用程序来扫描XAML文件并将其所有实例拉入Excel电子表格,然后将其发送给翻译人员,第二个实用程序将我们取回的翻译内容打包到XML文件中(每种语言一个) 。这些文件基本上是字典,其中XAML中的英语文本用作查找当前所选语言翻译的关键:

<Translation key="Scan Passport" text="扫描护照" />

这有很多好处:

  • 开发人员仍然使用通用语言(在我的情况下为英语)编写XAML。
  • 您不必在每次向前端添加新文本时都重建解决方案中的每个项目。
  • 如果您添加尚未翻译的新文本,则 “翻译”扩展名只是回溯到英语 翻译。
  • XML文件存储在本地,因此客户端可以随意更改本地化文本(包括英语)。
  • 您可以完全控制GUI中的哪些字段进行翻译,哪些不进行翻译。

当然,如果您在运行时更改翻译,则正在翻译的控件都会自动更新并立即切换到新语言。

显然,该系统的关键是编写“翻译”自定义标记扩展名,幸运的是,有人已经为您完成了该任务:

https://www.wpftutorial.net/LocalizeMarkupExtension.html

答案 1 :(得分:0)

好吧,花了几天时间进行黑客攻击,但我最终知道了这一点。在MarkupExtensions的ProvideValue调用中,您可以获取IProvideValueTarget服务来获取目标。这使您可以做两件事。首先,您可以检查目标是否为空,从而绕过初始启动调用并延迟绑定,直到应用数据模板为止。其次,一旦应用了模板,您就可以获取对象的数据上下文,从而使您能够对其进行反映,从而无需在设计时声明它(我的最终目标)。

这是我的MarkupExtension类的ProvideValue函数:

foo

最终结果是我可以指定组合框项目源,而无需指定单个静态类型:

public override object ProvideValue(IServiceProvider serviceProvider)
{
    //Get the target control
    var pvt = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
    if (pvt == null) { return null; }
    var target = pvt.TargetObject as FrameworkElement;

    //If null then return the class to bind at runtime.
    if (target == null) { return this; }

    if (target.DataContext.GetType().IsEnum)
    {
            Array enumValues = Enum.GetValues(target.DataContext.GetType());
            return enumValues;                
    }
    return null;
}