Многоязычный ComboBox, связанный с перечислением описаний в DataTemplate с использованием DataTemplateSelector - PullRequest
0 голосов
/ 10 декабря 2018

Я работаю над чем-то, где отдельные части были хорошо обсуждены, но у меня проблемы с соединением их всех.У нас есть приложение, которое имеет множество плагинов, требующих различных входных параметров, которые я пытаюсь сделать многоязычными.Я работал над динамическим графическим интерфейсом, который проверяет плагин для создания массива входных параметров и использует DataTemplateSelector, чтобы выбрать правильный элемент управления в зависимости от типа параметра.Для перечислителей мы пытаемся связать локализованное отображаемое имя со списком.В StackOverflow много потоков о том, как выполнять привязку перечислений / списков, но я не нашел многоязычных и динамических (datatemplate или других).

У Брайана Лагунаса есть отличная запись в блоге, которая почти привела нас туда: 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
}

Вот преобразователь типа enum:

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

Вот расширение разметки источника привязки Enum:

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"/

Я поэкспериментировал с этим в коде окна, селекторе шаблонов данных и даже в пользовательском элементе управления без особого успеха.Мое первое «настоящее» приложение WPF, так что я, по общему признанию, немного вне своей лиги, собирая все это вместе против их отдельных частей.

Вот пример запуска

Ответы [ 2 ]

0 голосов
/ 12 декабря 2018

Хорошо, заняло несколько дней взлома, но я наконец понял это.В вызове ProvideValue MarkupExtensions вы можете получить сервис IProvideValueTarget для получения цели.Это позволяет вам сделать две вещи.Во-первых, вы можете проверить, является ли цель нулевой, и, таким образом, обойти начальный вызов запуска и отложить привязку до применения таблицы данных.Во-вторых, после применения шаблона вы можете получить datacontext объекта, который позволит вам отразить его и тем самым избавить от необходимости объявлять его во время разработки (моя конечная цель).

Вот мой класс MarkupExtension 'Функция ProvideValue:

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

Конечным результатом является то, что я могу указать источник элементов списка со списком без указания одного статического типа:

<ComboBox ItemsSource="{local:EnumBindingSource}"
0 голосов
/ 10 декабря 2018

Одним из возможных «решений» этого является полное исключение использования перечислений для локализации.Это то, что я делаю, это позволяет мне и другим разработчикам проекта вставлять простой английский в наш XAML следующим образом:

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

Я написал небольшую утилиту для сканирования наших файлов XAML и извлечения всех экземпляров этих файлов.в электронную таблицу Excel, которая отправляется переводчикам, вторая утилита берет переводы, которые мы получаем, и упаковывает их в файлы XML (по одному на язык).Эти файлы в основном являются словарями, где английский текст в XAML используется в качестве ключа для поиска перевода на текущем выбранном языке:

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

Это имеет ряд преимуществ:

  • Разработчики по-прежнему пишут свой XAML на общем языке (в моем случае - на английском).
  • Вам не нужно перестраивать каждый проект в своем решении каждый раз, когда вы добавляете новый текст в интерфейс.
  • Если вы добавляете новый текст, который еще не был переведен, то расширение `Translate 'просто возвращается к английскому переводу.
  • Файлы XML хранятся локально, поэтому клиент может свободноизменить текст локализации по желанию (включая английский).
  • Вы имеете полный контроль над тем, какие поля в вашем графическом интерфейсе проходят перевод, а какие нет.

И, конечно, если выизмените перевод во время выполнения, тогда переводимые элементы управления обновятся и сразу переключатся на новый язык.

Ключу этой системы, очевидно, написано, что расширение пользовательской разметки 'Translate', к счастью, кто-то уже сделал это для вас:

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

...