Шаблоны данных и универсальные шаблоны

Я прочитал около тысячи сообщений, объясняющих, что установка закрытого универсального типа как 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" };
    }
}

При такой настройке вы увидите «Hi List of Strings». Для меня это доказательство того, что я могу определять универсальные типы как 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 для разрешения XamlType используется не TypeExtension, но я думаю, что используется XamlNamespace. Поскольку в этом классе есть метод MangleGenericTypeName, в котором используется символ `-character. Но я все еще не вижу код, который извлекает аргументы типа, поэтому я не вижу правильный синтаксис для указания аргументов типа для Словаря. Вот где я застрял.

(Излишне говорить, что Microsoft-Docs бесполезны по этой теме.)

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

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

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

Здесь нет общих типов. У вас тип конкретный, List<string>. Общий тип - List<T>. Вы не можете использовать универсальные типы просто потому, что компилятор не может знать, что входит в эти универсальные типы.

Panagiotis Kanavos 08.01.2019 14:29

И нет, документы MS небезупречны, и не все эти люди говорят, что вы не можете использовать общие типы неправильно. WPF вышел лет 10 назад, ведь кто-то заметил бы. Практически все руководства и документы все равно показывает привязку к универсальным типам, будь то List<SomeEntity> или ObservableCollection<SomeOtherEntity> или что-то, возвращаемое EF

Panagiotis Kanavos 08.01.2019 14:31

Неважно, что вопрос следует закрыть как есть, и я не голосовал против. Как вы думаете, зачем вам указывать такой тип? Какую проблему действительный вы хотите решить? Шаблон данных обычно используется для отображения Предметы в контейнере, поэтому вы будете видеть строки как DataType, но никогда не IEnumerable <string>. Контейнеры визуализируются элементами управления, производными от ItemsControls, которые уже знают об IEnumerable.

Panagiotis Kanavos 08.01.2019 14:37

И, наконец, вам даже не нужно указывать DataType в DataTemplate. Связывание данных WPF работает с отражением, поэтому вам не нужно указывать тип, если объект, к которому привязывается шаблон, имеет свойства, удовлетворяющие выражениям привязки.

Panagiotis Kanavos 08.01.2019 14:40

@Panagiotis Kanavos: Думаю, вы упустили суть. Кроме того, первый абзац адресован всем тем людям, которые сказали, что я должен унаследовать от List<string> и создать неуниверсальный тип, закрывающий List<T>. Пример: stackoverflow.com/a/10005490/1353211. И если почти все учебники и документы все равно показывает привязку к универсальным типам, почему бы вам просто не ответить на вопрос?

Rico-E 08.01.2019 14:48

@Panagioti Вопрос мне тоже не имеет смысла. Однако в примере, построенном в вопросе, свойство DataType необходимо для автоматического выбора ContentTemplate ContentControl.

Clemens 08.01.2019 14:48

@ Rico-E Совершенно непонятно, зачем вообще нужно назначать экземпляр коллекции свойству Content ContentControl. В этом нет особого смысла. Вместо этого вы должны назначить коллекцию свойству ItemsSource элемента управления ItemsControl и иметь DataTemplate (без DataType) в качестве ItemTemplate элемента управления ItemsControl.

Clemens 08.01.2019 14:55

@Clemens, и для отображения элементов все равно потребуется ItemsControl. Даже если потребуется какое-то преобразование, конвертер выполнит эту работу. Нет необходимости создавать расширение разметки

Panagiotis Kanavos 08.01.2019 14:55

@ Rico-E то, что вы хотите, уже доступно. Ваши предположения неверны - вы не можете использовать универсальный тип без указания параметров типа. Ваш код все равно этого не делает. Я подозреваю, что вам стоит изучить руководства по привязке данных. Например, вы не устанавливаете свойство содержимого напрямую. Это привязывает View к ViewModel. Вы позволяете выражениям привязки данных связывать каждый атрибут с типом

Panagiotis Kanavos 08.01.2019 14:58

@ Rico-E, про имена типов тоже ничего недокументированного нет. Однако люди просто не делают того, что вы пытались сделать. Элементы обычно имеют более двух свойств, что значительно упрощает использование ItemsControl, DataTemplate и привязок непосредственно к именам свойств.

Panagiotis Kanavos 08.01.2019 15:01

@ Rico-E кто-нибудь другой проголосовал против этого вопроса. Все остальные отказались от этого. Вместо того, чтобы придираться к тем, кто пытался помочь (Клеменс и я), считайте, что вопрос не был ясен до тех пор, пока он не был отредактирован, и люди в любом случае не работают таким образом.

Panagiotis Kanavos 10.01.2019 10:25

@ Rico-E способ привязки легкий к любому контейнеру заключается в создании класса ViewModel с одним свойством, содержащим список или словарь, и реализации INotifyPropertyChanged.

Panagiotis Kanavos 10.01.2019 10:27
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
12
1 117
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Напишите 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 автоматически, используйте код программной части:

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

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

Но в моем проекте у меня есть специальная сборка для стилей, в которых я пишу все свои DataTemplate и ControlTemplate. Обычно у меня есть 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>

В качестве обходного пути я теперь определяю свои DataTemplate в ResourcesFrameworkElement и добавляю их в код программной части 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);

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

Эй, этот метод идеально подходит для моего варианта использования, и он работал, когда я в первый раз запускал свою программу, но в следующий раз, без каких-либо изменений кода, он завершился ошибкой сборки: «Все объекты, добавленные в IDictionary, должны иметь атрибут Key ... "в определении DataTemplate. Я пробовал строить, перестраивать, все, что я мог придумать, но единственный способ избавиться от ошибки сборки - заменить DataType в DataTemplate обычным типом :( Есть идеи? <DataTemplate DataType = "{StaticResource OrderViewDataType}">

Frederik 23.11.2021 13:46

<x: Array Type = "{x: Type System: Type}" x: Key = "OrderViewDataArray"> <x: Type TypeName = "viewData: OrderViewData" /> </ x: Array> <helpers: GenericType BaseType = " {x: Type TypeName = viewData: TabViewData`1} "InnerTypes = " {StaticResource OrderViewDataArray} "x: Key = " OrderViewDataType "/> TabViewData <T> где T: IsTab OrderViewData: IsTab

Frederik 23.11.2021 13:46

Хорошо, я "исправил" это, заменив назначение DataTemplate DataType на <DataTemplate.DataType> <StaticResource ResourceKey = "OrderViewDataType" /> </DataTemplate.DataType>. Представление дизайнера прерывается, если я помещаю каретку в вышеупомянутый код, но я могу с этим жить. Надеюсь, кто-то еще сможет это использовать :)

Frederik 23.11.2021 14:12

И он снова сломан. Я работал над другими частями своего проекта, пытался построить. Бам: «Ключ для словаря не может быть типа 'System.Windows.StaticResourceExtension'» Я не понимаю ... Извините за спам в вашем сообщении.

Frederik 23.11.2021 15:05

Я создал свой вопрос: stackoverflow.com/questions/70082742/…

Frederik 23.11.2021 15:40

Другие вопросы по теме