Я пишу Пользовательский элемент управления WPF для своего приложения, перенося ListBox и несколько других объектов.
ListBox имеет новый ItemTemplate, который представляет четыре сведения для каждого объекта в моем списке. Я могу трудно кодировать каждую эти четыре привязки к определенным свойствам на моих элементах списка, и они отображаются прекрасный.
Однако я хочу, чтобы мой UserControl был немного более гибким.
На ListBox и ComboBox там свойство DisplayMemberPath (наследованный от ItemsControl), который, кажется, "вводит" соответствующую привязку свойства в стандартный ItemTemplate.
Как я достигаю того же результата со своим пользовательским элементом управления?
Я хотел бы настроить четыре новых свойства для разрешения конфигурации отображенной информации:
public string LabelDisplayPath { get; set; }
public string MetricDisplayPath { get; set; }
public string TitleDisplayPath { get; set; }
public string SubtitleDisplayPath { get; set; }
Рассмотрение ItemsControl. DisplayMemberPath с Отражателем, кажется, спускается по кроличьей норе, я не смог понять, как он работает.
Кроме того, если я полностью от курса - и существует другой, больше техники "WPF", которую я должен использовать вместо этого, указывает на меня в том направлении.
Обновление
Вот разъяснение того, чего я пытаюсь достигнуть.
ListBox в моем пользовательском элементе управления отображает четыре сведения на объект: Маркировка, Заголовок, Подзаголовок и Метрика
В одном месте я хочу использовать этот Пользовательский элемент управления для отображения списка проблем. Каждая проблема похожа на это:
public class Issue {
public string Code { get; set; }
public string Description { get; set; }
public string Priority { get; set; }
public string Reporter { get; set; }
}
При отображении проблем я хочу использовать следующие отображения:
Code --> Label
Description --> Title
Reporter --> Subtitle
Priority --> Metric
В другом месте в том же приложении, у меня есть список Сообщений, что я хочу отобразить использование того же UserControl. Каждое Сообщение похоже на это:
public class Post {
public DateTime PostedOn { get; set; }
public string Title { get; set; }
public string Teaser { get; set; }
public int CommentCount { get; set; }
}
При показывании сообщений я хочу использовать следующие отображения:
PostedOn --> Label
Title --> Title
Teaser --> Subtitle
CommentCount --> Metric
Учитывая, что Проблемами и Сообщениями являются очень отличающиеся абстракции, я не хочу вынуждать их иметь те же свойства, только позволить UserControl использоваться неизменный. Вместо этого я хочу представить немного конфигурируемости так, чтобы я мог снова использовать свой UserControl в обоих сайтах чисто.
Я нашел решение своей проблемы - это немного сложнее, чем хотелось бы ...
В любом элементе ItemsControl, когда вы устанавливаете свойство DisplayMemberPath , экземпляр внутренний класс DisplayMemberTemplateSelector
назначен ItemTemplateSelector . Фактически, этот класс генерирует настраиваемый шаблон DataTemplate с соответствующей привязкой в соответствии с текущей конфигурацией DisplayMemberPath. Новый экземпляр этого класса создается и используется каждый раз, когда изменяется DisplayMemberPath
.
Чтобы воспроизвести это в моем собственном Usercontrol, вместо изменения существующего шаблона данных на лету, как я изначально планировал, мне нужно создать свой собственный вспомогательный класс для выбора шаблона и имитировать тот же подход, что и ItemsControl.
Я бы использовал ViewModels в качестве оберток в этом случае, чтобы объединить два случая: Вы можете создать (абстрактный) класс ItemViewModelBase
, который определяет свойства Label
, Title
, Subtitle
и Metric
. Затем вы создаете конкретные классы, производные от этого базового VM, для всех элементов, которые вы хотите отображать с помощью одного элемента управления. Каждый из этих классов возвращает что-то свое в свойствах.
Таким образом, вы можете определить один DataTemplate
для класса ItemViewModelBase
, и он будет применяться к обоим типам элементов.
Некоторый код может сделать это более понятным:
public abstract class ItemViewModelBase
{
public abstract string Label { get; }
public abstract string Title { get; }
public abstract string Subtitle { get; }
public abstract string Metric { get; }
}
public class IssueViewModel : ItemViewModelBase
{
private Issue _issue;
public override string Label { get { return _issue.Code; } }
public override string Title { get { return _issue.Description; } }
public override string Subtitle { get { return _issue.Reporter; } }
public override string Metric { get { return _issue.Priority; } }
public IssueViewModel(Issue issue)
{
_issue = issue;
}
}
public class PostViewModel : ItemViewModelBase
{
private Post _post;
public override string Label { get { return _post.PostedOn.ToString(); } }
public override string Title { get { return _post.Title; } }
public override string Subtitle { get { return _post.Teaser; } }
public override string Metric { get { return _post.CommentCount.ToString(); } }
public PostViewModel(Issue post)
{
_post= post;
}
}
В вашем ListBox вы будете отображать коллекцию экземпляров ItemViewModelBase
, а не Issue
и Post
, которые будут отображаться с помощью одного общего DataTemplate
, вот так:
<DataTemplate DataType="{x:Type local:ItemViewModelBase}">
<StackPanel>
<TextBlock x:Name="txtLabel" Text="{Binding Label}"/>
<TextBlock x:Name="txtTitle" Text="{Binding Title}"/>
<TextBlock x:Name="txtSubtitle" Text="{Binding Subtitle}"/>
<TextBlock x:Name="txtMetric" Text="{Binding Metric}"/>
</StackPanel>
</DataTemplate>
Конечно, ваш DataTemplate
будет выглядеть по-другому, приведенный выше пример просто демонстрирует принцип. Кроме того, вы можете определить другие типы возврата для свойств - я сделал их все строками, хотя в ваших классах данных есть DateTime
и int
. Еще одна вещь, которую я бы не стал делать в производственном коде: DateTime.ToString()
, как я сделал в PostViewModel.Label
- лучше замените его каким-нибудь локализованным строковым представлением.
Думаю, лучше всего использовать шаблоны данных.
Если вы хотите сделать его гибким, то определите шаблон данных по умолчанию в вашей библиотеке ресурсов (это можно сделать, просто не определяя x:Key или x:Name для элемента). Создайте один шаблон данных для каждого класса, который вы хотите отобразить. Затем просто привяжите/заполните список классами, и они будут отображаться с использованием шаблона данных по умолчанию :)
После множества подсказок из вашего обновления и комментариев, я думаю, вы можете добиться этого, используя dependencyProperty
Позвольте мне обрисовать идею
XAML:
<UserControl x:Name="myControl">
<StackPanel>
<TextBlock x:Name="textBlock"
Text="{Binding ElementName=myControl, Path=LabelDisplayPath}" />
<TextBlock x:Name="textBlock"
Text="{Binding ElementName=myControl, Path=MetricDisplayPath}" />
<TextBlock x:Name="textBlock"
Text="{Binding ElementName=myControl, Path=TitleDisplayPath}" />
<TextBlock x:Name="textBlock"
Text="{Binding ElementName=myControl, Path=SubtitleDisplayPath}" />
</StackPanel>
</UserControl>
где DisplayPaths - это зависимость properties
Теперь создайте эти Свойства зависимостей в коде UserControl позади: LabelDisplayPathProperty, MetricDisplayPathProperty ...
Теперь вы можете использовать их как встроенное свойство DisplayMemberPath, например:
<ListView ItemsSource="{Binding IssueList}">
<ListView.ItemTemplate>
<DataTemplate>
<myUC:Control1 LabelDisplayPath = "{Binding Path=Issue.Code}"
MetricDisplayPath = "{Binding Path=Issue.Priority}"
TitleDisplayPath = "{Binding Path=Issue.Description}"
SubtitleDisplayPath = "{Binding Path=Issue.Reporter}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>