DataTemplates и Generics - PullRequest
       48

DataTemplates и Generics

0 голосов
/ 08 января 2019

Я прочитал почти тысячу постов, объясняющих, что установка закрытого универсального типа как DataType на DataTemplate не работает, потому что WPF не будет поддерживать это. Но на самом деле это просто неправильно.

Я могу определить следующее DataTemplate в моем Window.Resources, и оно будет использовано при назначении списка строк для элемента управления контентом. Например:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate DataType="{x:Type TypeName=Generic:List`1[System.String]}">
            <TextBlock Text="Hi List of Strings"
                       FontSize="40"
                       Foreground="Cyan"/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ContentControl x:Name="_contentControl">
        </ContentControl>
    </Grid>
</Window>

и в коде позади:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        _contentControl.Content = new List<string> { "Huhu" };
    }
}

При этой настройке вы увидите «Привет список строк». Для меня это доказательство того, что я могу определить универсальные типы как DataType. Но я хочу сделать еще один шаг вперед: я бы хотел определить Dictionary<string, string> как DataType. Но, к сожалению, я не могу заставить его работать.

Итак, вопрос : Как я могу определить Dictionary<string, string> как DataType из DataTemplate?

Если вы знаете ответ, вы можете перестать читать. Но поскольку хорошей практикой является показать, что я уже сделал, я продолжаю писать. Что я уже сделал? Сначала я пошел грубой силой и попробовал несколько комбинаций, подобных:

- DataType="{x:Type TypeName=Generic:Dictionary`2[System.String];[System.String]}"
- DataType="{x:Type TypeName=Generic:Dictionary`2[System.String],[System.String]}"
- DataType="{x:Type TypeName=Generic:Dictionary`2[System.String,System.String]}"

Но так как ни один из них не работал, я углубился в System.Xaml и посмотрел на TypeExtension, GenericTypeNameParser и GenericTypeNameScanner, потому что я думал, что это кодовые строки, которые разрешают тип. Но, глядя на код, я понял, что `это недопустимый символ.

Чтобы доказать это, я написал свой MarkupExtension

public class UseTheTypeExtensionsParser : MarkupExtension
{
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var a = new TypeExtension("Generic:List`1[[System.String]]");
        var type = a.ProvideValue(serviceProvider);
        return type.ToString();
    }
}

и использовал его следующим образом:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
        xmlns:WpfApp1="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ContentControl Content="{WpfApp1:UseTheTypeExtensionsParser}"/>
    </Grid>
</Window>

И это породило исключение, что символ `не ожидался и что тип xaml недействителен.

Это заставило меня задуматься, почему мой первый пример сработал. Я думаю, что при компиляции разметки xaml для wpf это не TypeExtension, который используется для разрешения XamlType, но я думаю, что используется XamlNamespace. Потому что у этого класса есть MangleGenericTypeName -метод, который использует `-характер. Но я все еще не вижу код, который извлекает аргументы типа, поэтому я не вижу правильный синтаксис для указания аргументов типа для словаря. Вот где я застрял.

(Само собой разумеется, что Microsoft-Docs бесполезны в этой теме.)

Редактировать : Поскольку кажется неясным, почему я этого хочу, я объясню это: я хочу автоматический выбор ContentTemplate ContentControl. И конечно: мой построенный DataTemplate в примере очень прост. Но каждый должен представить, что мне нужны разные шаблоны данных для списков, словарей или простых строк.

У меня есть ViewModel, которая имеет свойство public object Result { get; }. И иногда, результатом является int, иногда строка, иногда List и так далее, и так далее. Я связываю это Result -свойство с Content -Свойством ContentControl. И для всех упомянутых типов я написал разные шаблоны данных, которые автоматически выбираются WPF. Таким образом, int s показаны в Rectangle, а String s показаны в Ellipse.

.

После того, как я все это заработал, я хочу другой DataTemplate, но на этот раз для словаря.

1 Ответ

0 голосов
/ 10 января 2019

Я получил его для работы со следующим кодом:

Напишите MarkupExtension, которое возвращает закрытый универсальный тип, который вы хотите использовать в качестве DataType для вашего DataTemplate (это не мое. Это где-то из SO, но я не сохранил ссылку).

public class GenericType : MarkupExtension
{
    public GenericType() { }

    public GenericType(Type baseType, params Type[] innerTypes)
    {
        BaseType = baseType;
        InnerTypes = innerTypes;
    }

    public Type BaseType { get; set; }

    public Type[] InnerTypes { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        Type result = BaseType.MakeGenericType(InnerTypes);
        return result;
    }
}

Используйте его следующим образом:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
        xmlns:WpfApp1="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <x:Array Type="{x:Type System:Type}" 
                 x:Key="ListWithTwoStringTypes">
            <x:Type TypeName="System:String" />
            <x:Type TypeName="System:String" />
        </x:Array>

        <WpfApp1:GenericType BaseType="{x:Type TypeName=Generic:Dictionary`2}" 
                           InnerTypes="{StaticResource ListWithTwoStringTypes}"
                           x:Key="DictionaryStringString" />

        <DataTemplate DataType="{StaticResource DictionaryStringString}">
            <TextBlock Text="Hi Dictionary"
                   FontSize="40"
                   Foreground="Cyan"/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ContentControl x:Name="_contentControl"/>
    </Grid>
</Window>

Чтобы увидеть, автоматически ли применяется DataTemplate, используйте команду write в коде:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        _contentControl.Content = new Dictionary<string, string>();
    }
}

И вы увидите свой DataTemplate.

Но в моем проекте у меня есть специальная сборка для стилей, в которой я пишу все свои DataTemplates и ControlTemplates. Обычно у меня есть ResourceDictionary, который содержит их. Но когда я хочу поместить свой DataTemplate в ResourceDictionary, компилятор говорит мне, что у него не будет ключа.

Это не работает:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:System="clr-namespace:System;assembly=mscorlib"
                    xmlns:DataTemplates="clr-namespace:Dana.Styles.Flat.DataTemplates"
                    xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib">


    <x:Array Type="{x:Type System:Type}" 
             x:Key="ListWithTwoStringTypes">
        <x:Type TypeName="System:String" />
        <x:Type TypeName="System:String" />
    </x:Array>

    <DataTemplates:GenericType BaseType="{x:Type TypeName=Generic:Dictionary`2}" 
                               InnerTypes="{StaticResource ListWithTwoStringTypes}"
                               x:Key="DictionaryStringString" />

    <DataTemplate DataType="{StaticResource DictionaryStringString}">

        <TextBlock Text="Hi Dictionary"
                   FontSize="40"
                   Foreground="Cyan"/>
    </DataTemplate>

</ResourceDictionary>

В качестве обходного пути теперь я определяю свои DataTemplates в ресурсах FrameworkElement и добавляю их в виде кода в Application.Resources.

Это DictionaryStringString.xaml

<FrameworkElement x:Class="Dana.Styles.Flat.DataTemplates.DictionaryStringString"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:Generic="clr-namespace:System.Collections.Generic;assembly=mscorlib"
             xmlns:DataTemplates="clr-namespace:Dana.Styles.Flat.DataTemplates"
             xmlns:System="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <FrameworkElement.Resources>

        <x:Array Type="{x:Type System:Type}" 
                 x:Key="ListWithTwoStringTypes">
            <x:Type TypeName="System:String" />
            <x:Type TypeName="System:String" />
        </x:Array>

        <DataTemplates:GenericType BaseType="{x:Type TypeName=Generic:Dictionary`2}" 
                                   InnerTypes="{StaticResource ListWithTwoStringTypes}"
                                   x:Key="DictionaryStringString" />

        <DataTemplate DataType="{StaticResource DictionaryStringString}">

            <TextBlock Text="Hallo Wörterbuch"
                           FontSize="40"
                           Foreground="Cyan"/>Template>
            </ItemsControl>-->
        </DataTemplate>
    </FrameworkElement.Resources>
</FrameworkElement>

Это DictionaryStringString.xaml.cs:

public partial class DictionaryStringString
{
    /// <summary>
    /// Konstruktor
    /// </summary>
    public DictionaryStringString()
    {
        InitializeComponent();
    }
}

И затем, где я инициализирую свои стили, я добавил:

var _dictionaryStringString = new DictionaryStringString();
Application.Current.Resources.MergedDictionaries.Add(_dictionaryStringString.Resources);

И теперь я могу определить DataTemplates для всех закрытых универсальных типов и автоматически применять их с помощью WPF =)

...