Визуальные дополнения

119

»» СКАЧАТЬ ИСХОДНИКИ ПРОГРАММЫ (VS 2012)

Учитывая тот факт, что WPF является визуальной технологией, наверняка интересует вопрос — как заставить дополнения генерировать пользовательский интерфейс? Это не такая простая задача. Проблема в том, что элементы пользовательского интерфейса в WPF не сериализуемы. Поэтому они не могут передаваться между приложением-хостом и дополнением.

К счастью, проектировщики системы дополнений предусмотрели изощренный обходной путь. Решение заключается в том, чтобы позволить приложениям WPF отображать содержимое пользовательского интерфейса, размещенного в разных доменах приложений. Другими словами, приложение-хост может отображать элементы управления, которые на самом деле работают в домене дополнения. При взаимодействии с этими элементами управления (щелчки, ввод текста и т.п.) инициируются события в домене дополнения. Для передачи информации из дополнения в приложение и обратно используются интерфейсы контрактов, как было показано в предыдущих статьях.

На рисунке ниже показана эта техника в действии в модифицированной версии приложения обработки изображений. Когда выбрано дополнение, приложение-хост запрашивает у дополнения элемент управления с соответствующим содержимым. Этот элемент управления затем отображается в нижней части окна:

Визуальное дополнение

В этом примере выбрано дополнение, превращающее изображение в негатив. Оно представляет пользовательский элемент управления, который упаковывает элемент Image (с предварительным просмотром эффекта) и элемент Slider. По мере перемещения ползунка Slider меняется интенсивность эффекта, и изображение предварительного просмотра изменяется. (Процесс обновления довольно медлительный по причине плохой оптимизации кода обработки изображения. Для достижения максимальной производительности можно было бы воспользоваться гораздо более совершенным алгоритмами, возможно, включающими небезопасные блоки кода.)

Хотя механизм, выполняющий эту работу, довольно сложный, использовать его неожиданно легко. Ключевой ингредиент заключен в интерфейсе INativeHandleContract из пространства имен System.AddIn.Contract. Он позволяет передавать дескриптор окна между дополнением и приложением-хостом. Ниже приведен пересмотренный интерфейс IImageProcessorContract из сборки контракта. Он заменяет метод ProcessImageBytes() методом GetVisual(), принимающим те же данные изображения, но возвращающим порцию пользовательского интерфейса:

[AddInContract]
public interface IImageProcessorContract : IContract
{
        INativeHandleContract GetVisual(Stream imageStream);
}

Интерфейс INativeHandleContract не применяется в классах представления, потому что он не пригоден для непосредственного использования в WPF-приложениях. Взамен него применяется вполне ожидаемый тип FrameworkElement. Ниже показано представление хоста:

public abstract class ImageProcessorHostView
{
        public abstract FrameworkElement GetVisual(Stream imageStream);
}

А это почти идентичное представление дополнения:

[AddInBase]
public abstract class ImageProcessorAddInView
{        
        public abstract FrameworkElement GetVisual(Stream imageStream);
}

Этот пример неожиданно похож на вариант с автоматизацией из предыдущей статьи. Здесь передается в контракт другой тип, нежели тот, что используется в представлениях. Опять-таки, для преобразований "контракт-представление" и "представление-контракт" должны применяться адаптеры. Однако на этот раз работу выполняет специализированный класс по имени FrameworkElementAdapter.

FrameworkElementAdapter находится в пространстве имен System.AddIn.Pipeline, но не является частью WPF, а входит в сборку System.Windows.Presentation.dll. В классе FrameworkElementAdapter определены два статических метода, выполняющие работу по преобразованию: ContractToViewAdapter() и ViewToContractAdapter(). Ниже показано, как с помощью метода FrameworkElementAdapters.ContractToViewAdapter() заполнить пробел в адаптере хоста:

[HostAdapter]
public class ImageProcessorContractToViewHostAdapter : HostView.ImageProcessorHostView
{
        private Contract.IImageProcessorContract contract;
        private ContractHandle contractHandle;

        public ImageProcessorContractToViewHostAdapter(Contract.IImageProcessorContract contract)
        {            
            this.contract = contract;
            contractHandle = new ContractHandle(contract);
        }

        public override FrameworkElement GetVisual(Stream imageStream)
        {
            return FrameworkElementAdapters.ContractToViewAdapter(contract.GetVisual(imageStream));
        }
}

А вот как метод FrameworkElementAdapters.ViewToContractAdapter() позволяет заполнить пробел в адаптере дополнения:

[AddInAdapter]
public class ImageProcessorViewToContractAdapter : ContractBase, Contract.IImageProcessorContract
{
        private AddInView.ImageProcessorAddInView view;

        public ImageProcessorViewToContractAdapter(AddInView.ImageProcessorAddInView view)
        {
            this.view = view;
        }

        public INativeHandleContract GetVisual(Stream imageStream)
        {
            return FrameworkElementAdapters.ViewToContractAdapter(view.GetVisual(imageStream));
        }
}

И последний штрих заключается в реализации метода GetVisual() в дополнении. В приложении обработки негативного изображения создается новый элемент управления по имени ImagePreview. Данные изображения передаются этому элементу, который устанавливает изображение предварительного просмотра и обрабатывает щелчки на ползунке:

[AddIn("Инверсия цвета картинки", Version = "1.0.0.0",
        Publisher = "Imaginomics",
        Description = "Используется для иверсии цветов на картинке, чтобы получить эффект негатива пленки.")]
public class NegativeImageProcessor : AddInView.ImageProcessorAddInView 
{       
        public override FrameworkElement GetVisual(System.IO.Stream imageStream)
        {
            return new ImagePreview(imageStream);
        }
}

Теперь, когда известно, как вернуть объект пользовательского интерфейса из дополнения, ничто не ограничивает разновидность содержимого, которое можно генерировать. Базовая инфраструктура — интерфейс INativeHandleContract и класс FrameworkElementAdapters — остаются неизменными.

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