Я прочитал около тысячи сообщений, объясняющих, что установка закрытого универсального типа как 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.
И нет, документы MS небезупречны, и не все эти люди говорят, что вы не можете использовать общие типы неправильно. WPF вышел лет 10 назад, ведь кто-то заметил бы. Практически все руководства и документы все равно показывает привязку к универсальным типам, будь то List<SomeEntity> или ObservableCollection<SomeOtherEntity> или что-то, возвращаемое EF
Неважно, что вопрос следует закрыть как есть, и я не голосовал против. Как вы думаете, зачем вам указывать такой тип? Какую проблему действительный вы хотите решить? Шаблон данных обычно используется для отображения Предметы в контейнере, поэтому вы будете видеть строки как DataType, но никогда не IEnumerable <string>. Контейнеры визуализируются элементами управления, производными от ItemsControls, которые уже знают об IEnumerable.
И, наконец, вам даже не нужно указывать DataType в DataTemplate. Связывание данных WPF работает с отражением, поэтому вам не нужно указывать тип, если объект, к которому привязывается шаблон, имеет свойства, удовлетворяющие выражениям привязки.
@Panagiotis Kanavos: Думаю, вы упустили суть. Кроме того, первый абзац адресован всем тем людям, которые сказали, что я должен унаследовать от List<string> и создать неуниверсальный тип, закрывающий List<T>. Пример: stackoverflow.com/a/10005490/1353211. И если почти все учебники и документы все равно показывает привязку к универсальным типам, почему бы вам просто не ответить на вопрос?
@Panagioti Вопрос мне тоже не имеет смысла. Однако в примере, построенном в вопросе, свойство DataType необходимо для автоматического выбора ContentTemplate ContentControl.
@ Rico-E Совершенно непонятно, зачем вообще нужно назначать экземпляр коллекции свойству Content ContentControl. В этом нет особого смысла. Вместо этого вы должны назначить коллекцию свойству ItemsSource элемента управления ItemsControl и иметь DataTemplate (без DataType) в качестве ItemTemplate элемента управления ItemsControl.
@Clemens, и для отображения элементов все равно потребуется ItemsControl. Даже если потребуется какое-то преобразование, конвертер выполнит эту работу. Нет необходимости создавать расширение разметки
@ Rico-E то, что вы хотите, уже доступно. Ваши предположения неверны - вы не можете использовать универсальный тип без указания параметров типа. Ваш код все равно этого не делает. Я подозреваю, что вам стоит изучить руководства по привязке данных. Например, вы не устанавливаете свойство содержимого напрямую. Это привязывает View к ViewModel. Вы позволяете выражениям привязки данных связывать каждый атрибут с типом
@ Rico-E, про имена типов тоже ничего недокументированного нет. Однако люди просто не делают того, что вы пытались сделать. Элементы обычно имеют более двух свойств, что значительно упрощает использование ItemsControl, DataTemplate и привязок непосредственно к именам свойств.
@ Rico-E кто-нибудь другой проголосовал против этого вопроса. Все остальные отказались от этого. Вместо того, чтобы придираться к тем, кто пытался помочь (Клеменс и я), считайте, что вопрос не был ясен до тех пор, пока он не был отредактирован, и люди в любом случае не работают таким образом.
@ Rico-E способ привязки легкий к любому контейнеру заключается в создании класса ViewModel с одним свойством, содержащим список или словарь, и реализации INotifyPropertyChanged.





Я заставил его работать со следующим кодом:
Напишите 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}">
<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
Хорошо, я "исправил" это, заменив назначение DataTemplate DataType на <DataTemplate.DataType> <StaticResource ResourceKey = "OrderViewDataType" /> </DataTemplate.DataType>. Представление дизайнера прерывается, если я помещаю каретку в вышеупомянутый код, но я могу с этим жить. Надеюсь, кто-то еще сможет это использовать :)
И он снова сломан. Я работал над другими частями своего проекта, пытался построить. Бам: «Ключ для словаря не может быть типа 'System.Windows.StaticResourceExtension'» Я не понимаю ... Извините за спам в вашем сообщении.
Я создал свой вопрос: stackoverflow.com/questions/70082742/…
Здесь нет общих типов. У вас тип конкретный,
List<string>. Общий тип -List<T>. Вы не можете использовать универсальные типы просто потому, что компилятор не может знать, что входит в эти универсальные типы.