Давно не писал. Но время оно не резиновое...
За это время пример, описанный ранее (с биндингом FlowDocument) практически без доработок напильником был встроен в рабочий проект и работает.
Но сегодня про другое. В WPF есть классная штука, под названием RoutedUICommand. Если сильно упростить - то с ее помощью можно указывать какую команду будет выполнять кнопка или пункт меню. При этом сама реализации команды будет жить где-то отдельно. Причем для разных элементов управления может быть разная реализация (например команда "Сохранить" может сохранять как документ, так и какой-нибудь элемент справочника). Более того, в нужный момент WPF проверяет а можно ли вообще выполнит команду и если нельзя, то дизаблит кнопку или что у нас там к команде привязано. В общем не буду долго расписывать что это такое, кому интересно может
в журнале почитать.
Однако как всегда не все так хорошо как хотелось. В простом примере все шоколадно. Но как только мы начинаем все усложнять то нам сразу хочется тестов. А для такого случая умные дядьки придумали MVVM (опять так есть гугл и
журнал). В кратце смысл в том, что на форме у нас вообще никакого кода нет, а вся логика живет в View-модели, на которую настроены биндинги и которая разными шаманскими методами (самый простой - тупо назначить DataContext) подсовывается форме. А так как вся логика получсется живет в обычном классе (который о форме вообще ничего не знает), то его и тестировать можно очень легко.
И вот как раз на перекрестии двух очень интересных идей как обычно и живет засада. Заключается она в том, что команды привязываются через
CommandBinding. А он как обычно не является
DependencyObject и соответственно никакие его свойства мы не можем использовать в биндинге. Хотя как-бы и логично, потому что привязка в оригинале задумывалась как привязка к данным, а тут медоды... И соотв. нельзя написать что-то типа
- <UserControl.CommandBindings>
- <CommandBinding Command="ApplicationCommands.Save" Execute="{Binding SaveExecute}"/>
- UserControl.CommandBindings>
* This source code was highlighted with Source Code Highlighter.
(Идея не моя, где-то в блогах подсмотрел).
Получается что единственный более-менее стандартный метод - это описывать обработчики команд в коде формы (или UserControl-а, в общем в xaml.cs) и их этих обработчиков звать методы презентера. Что конечно можно, но не красиво.
Первым желанием что-либо сделать было создать универсальный обработчик команд, который через Reflection каждый раз лез бы в DataContext, искал там метод по атрибуту или еще как-нибудь и вызывал его. Однако посмотрев на частоту, с которой вызывается CanExecute возникли подозрения что тормозить будет безбожно... Немного почесав репу пришло в голову примерно следующее решение: так как презентер на самом деле меняется редко (чаще всего вообще назначается один раз при создании формы) то можно каждый раз отслеживать поменялся ли он, и лезть Reflection-ом только когда поменялся. А если ничего не менялось - то вызывать заранее запомненные (или созданные) делегаты. Такая вот эмуляция DataBinding-а.
Осталось определиться с тем, как связывать команды. Так как есть стандартный CommandBinding, то его и будем расширять. Вопрос только, как искать методы, исполняющие команду. Пока в голову пришло 2 варианта
- В презентере создавать экземпляр ICommand, и просто делегировать методы ему. При этом так как в презентере может быть много команд, то каким-то образом надо выбрать какая из них соответствует какому CommandBindig-у. Пока в голову пришло все команды в презентере делать определенным классом (DataContextCommand) у которого есть свойство, определяющее какую команду обслуживаем. Как это выглядит при использовании можно посмотреть тут.
- Пометить методы для CanExecute и Execute атрибутами. При этом в CommandBinding-е указать имя команды (к сожалению никак, кроме как строками не получается) и в атрибуте тоже указать имя команды. Как это выглядит при использовании можно посмотреть тут.
Долго расписывать реализацию не буду, вроде как не сильно сложно. Полный
пример (пока не нашел куда положить, выложил как и раньше на iFile.ru). Состоит из 3-х частей:
- StdCommand - почти целиком копия из примера для статьи в msdn magazine. Просто чтобы продемонстрировать как оно работает в простом случае.
- BindToICommand - реализация первого способа.
- NamedBind - реализация второго способа.
На самом деле в примере есть еще демонстрация некоторых хитростей, но их опишу позже.