Создание специального представления

75

Если GridView не отвечает существующим потребностям, можно создать собственное представление и расширить в нем возможности ListView. К сожалению, простым этот процесс назвать нельзя.

Понять, в чем заключается проблема, можно только узнав немного больше о том, как работают представления. А работают они за счет переопределения двух защищенных свойств: DefaultStyleKey и ItemContainerDefaultStyleKey. Каждое из этих свойств возвращает специальный объект под названием ResourceKey, который указывает на определенный в XAML стиль.

Свойство DefaultStyleKey указывает на стиль, который должен применяться для конфигурирования ListView в целом, а свойство ItemContainerDefaultStyleKey — на стиль, который должен использоваться для конфигурирования каждого имеющегося в этом ListView объекта ListViewItem. Хотя эти стили свободно могут настраивать любое свойство, обычно они выполняют свою работу путем замены шаблона ControlTemplate, который используется для ListView, и шаблона DataTemplate, применяемого для каждого ListViewItem.

Здесь как раз и возникают проблемы. Шаблон DataTemplate, отвечающий за отображение элементов, определяется в XAML-разметке. Давайте предположим, что вы хотите создать представление ListView, в котором бы для каждого элемента отображался фрагментированный мозаичным образом рисунок (плитка). Это довольно легко сделать с помощью DataTemplate: нужно всего лишь привязать свойство Source элемента Image к правильному свойству объекта данных. Но как узнать, какой объект данных предоставит пользователь?

Если вы жестко закодируете имена свойств в виде части представления, степень его полезности снизится, и использовать его повторно в других сценариях станет невозможно. Альтернативный вариант — принуждение пользователя предоставлять именно объект DataTemplate — не позволяет упаковать в представление такой объем функциональности, из-за чего использовать его повторно станет бессмысленно.

Прежде чем приступать к созданию специального представления, следует продумать, не удастся ли добиться точно такого же результата за счет просто использования правильного DataTemplate с ListBox или комбинации ListView/GridView.

Зачем же прилагать столько усилий для создания специального представления, если все нужные функциональные возможности можно получить за счет изменения стиля ListView (или даже ListBox)? Одна из главных причин связана с потребностью в получении списка, способного менять представления динамическим образом. Например, может быть необходим список товаров, допускающий просмотр в разных режимах в зависимости от выбора пользователя.

Реализовать такое поведение можно динамической подстановкой различных объектов DataTemplate (и такой подход вполне разумен), но часто представлению необходимо изменять не только объект DataTemplate элемента ListViewItem, но и компоновку или общий внешний вид самого элемента управления ListView. Представление помогает прояснить взаимосвязь между этими деталями в исходном коде.

В следующем примере демонстрируется создание сетки с возможностью плавного переключения с одного представления на другое:

Представление ImageView

Класс представления

Первым шагом, необходимым при разработке данного примера, является создание класса специального представления. Этот класс должен наследоваться от ViewBase. Вдобавок он обычно (хотя и не всегда) должен переопределять свойства DefaultStyleKey и ItemContainerDefaultStyleKey и предоставлять ссылки на стили.

В данном примере это представление называется TileView, поскольку его главной задачей является мозаичное отображение элементов во всем доступном пространстве. Для расположения содержащихся в нем объектов ListViewItem применяется WrapPanel. Это представление не называется ImageView потому, что мозаичное содержимое не является жестко закодированным и может вообще не включать никаких изображений. Вместо этого оно определяется с помощью шаблона, который предоставляется разработчиком при использовании TileView.

Класс TileView применяет два стиля: TileView (применяется к ListView) и TileViewItem (применяется к ListViewItem). Вдобавок доступно свойство по имени ItemTemplate, позволяющее использующему TileView разработчику предоставить правильный шаблон данных. Этот шаблон затем вставляется внутрь каждого элемента ListViewItem и применяется для создания мозаичного содержимого:

class TileView : ViewBase
    {
        private DataTemplate itemTemplate;
        public DataTemplate ItemTemplate
        {
            get { return itemTemplate; }
            set { itemTemplate = value; }
        }

        protected override object DefaultStyleKey
        {
            get
            {
                return new ComponentResourceKey(GetType(), "TileView"); 
            }
        }

        protected override object ItemContainerDefaultStyleKey
        {
            get
            {
                return new ComponentResourceKey(GetType(), "TileViewItem");
            }
        }
    }

Как видно, класс TileView делает не особо много. Он просто предоставляет ссылку ComponentResourceKey, которая указывает на правильный стиль.

ComponentResourceKey упаковывает два фрагмента информации: тип класса, который владеет стилем, и описательную строку ResourceId, которая идентифицирует ресурс. В данном примере типом класса для обоих ключей ресурсов является TileView. Описательные имена ResourceId не столь важны, но должны быть согласованными. В данном примере ключ стиля по умолчанию называется TileView, а ключ стиля для каждого элемента ListViewItem — TileViewItem. В следующей статье эти стили рассматриваются более подробно.

Пройди тесты
Лучший чат для C# программистов