Как реализовать DisplayMemberPath для моего Wpf UserControl? - PullRequest
5 голосов
/ 05 мая 2010

Я пишу пользовательский элемент управления 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 с помощью Reflector, кажется, идет по кроличьей норе, я не смог понять, как это работает.

Кроме того, если я совершенно не в курсе - и есть еще одна, более "техника 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 на обоих сайтах.

Ответы [ 4 ]

2 голосов
/ 08 мая 2010

Я нашел решение своей проблемы - это немного сложнее, чем хотелось бы ...

В любом ItemsControl, когда вы устанавливаете свойство DisplayMemberPath , экземпляр внутреннего класса DisplayMemberTemplateSelector назначается для ItemTemplateSelector . По сути, этот класс генерирует пользовательский шаблон DataTemplate с соответствующей привязкой в ​​соответствии с текущей конфигурацией DisplayMemberPath. Новый экземпляр этого класса создается и используется каждый раз, когда изменяется DisplayMemberPath.

Чтобы воспроизвести это на моем собственном Usercontrol, вместо того, чтобы изменять существующий шаблон данных на лету, как я планировал, мне нужно создать свой собственный вспомогательный класс для выбора шаблона и имитировать тот же подход, что и ItemsControl.

1 голос
/ 05 мая 2010

Я бы использовал ViewModels в качестве обёрток в этом случае, чтобы объединить два случая: Вы можете создать (абстрактный) класс ItemViewModelBase, который определяет свойства Label, Title, Subtitle и Metric. Затем вы создаете конкретные классы, производные от этой базовой виртуальной машины для всех элементов, которые вы хотите отобразить, используя тот же элемент управления. Каждый из этих классов возвращает что-то свое в свойствах.
Таким образом, вы можете определить one 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 экземпляров, которые будут отображаться с использованием one common 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 - лучше заменить его на какое-нибудь локализованное строковое представление.

0 голосов
/ 05 мая 2010

Я думаю, что вам лучше всего использовать шаблоны данных.
Если вы хотите сделать его гибким, то определите шаблон данных по умолчанию в своей библиотеке ресурсов (вы можете сделать это, просто не задав x: Key или x: Name для элемента). Создайте один шаблон данных для каждого класса, который вы хотите отобразить. Затем просто свяжите / заполните свой список классами, и они будут отображаться с использованием шаблона данных по умолчанию:)

0 голосов
/ 05 мая 2010

После множества подсказок из вашего обновления и комментариев, я думаю, вы можете добиться этого, используя 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 - свойства зависимостей

Теперь создайте эти свойства зависимостей в коде 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>
...