Сюжет моего следующего поста навеян моими техническими приключениями. Многие думают, что работа программиста это рутина, но для меня это ежедневные искрометные схватки! Чем сложнее проблема, тем приятнее ее решать =).
Проект, который я веду, "is standing on the shoulders" проекта-предка, и в связи с этим мне приходится использовать функциональность предыдущего (или может правильнее параллельного проекта) в базовой функциональности которого не заложена совместимость с новыми технологиями, да собственно и не должна быть.
Речь здесь идет в общем-то о простых вещах - поддержка интерфейсов в объектах для data binding. Так вот, используя объекты "старого движка", назовем это так, и даже наследуясь от них пропадает та легкость и элегантность, кода, которая присуща в Binding Oriented Programming (Paul Stauvell, можно почитать здесь и здесь). Так что же делать, если я не хочу писать классы обертки каждый раз как мне надо будет использовать класс из старой функциональности, или не хочу писать spaghetti-code?
Тут и пришла идея использовать прием описанный WPF rock-star Josh'ем Smith'ом здесь.
Прием был разработан Josh'ем немного для другой ситуации - для нормального обновления UI, когда binding происходит на сам объект с интерфейсом INotifyPropertyChanged (INPC), а не на его свойства, и они (эти свойства) обновляются.
Суть решения состоит в следующем - объект подменяется оберткой(proxy, wrapper), которая реализует интерфейс INPC, и генерирует событие PropertyChanged при доступе и изменении свойств оборачиваемого объекта. Оборачиваемый объект же выставляется наружу через bindng converter.
В нашем случае интерфейс INPC может быть не реализован или реализован частично! Для этого случая и была расширена функциональность BuisnessObjectHolder от Josh'а. Интерфейс класса:
Класс наследуется от базового класса, реализующего интерфейс INPC - ObservableObject, который опять же был навеян постами Josh'а здесь. В интерфейс класса добавлены 2 internal метода - SetValueProperty и GetValueProperty. Их использует binding converter, о котором чуть позже.
При создании wrapper'a в конструктор ему передается оборачиваемый объект. Из него при помощи TypeDescriptor'а мы получаем список public PropertyDescriptor'ов и кешируем их, и, если класс реализует INPC, подписываемся на PropertyChanged.
Вот здесь на сцену выходит GenericObservableObjectConverter - binding converter, который будет выставлять наружу обернутый объект, или его свойства, имена которых, передаются в Binding через ConverterParameter.
Конвертер работает следующим образом:
- При передаче ему объекта от binding source проверяет создан ли wrapper, если нет создает его, и в зависимости от переданного параметра возвращает в UI значения: ConverterParameter = null - wrapper; ConverterParameter = propertyName - своство обернутого объекта с propertyName, вызвав у wrapper'а GetValueProperty();
- В обратном процессе при передаче значений от binding target к binding source конвертер проверяет значение ConverterParameter и если оно отлично от null вызывает SetValueProperty() wrapper'а.
SetValueProperty ObservableObjectWrapper'а вызывает Value_PropertyChanged, заставляя UI обновить весь объект.
Обратите внимание, что сначала объект обнуляется, это связано с тем, что wpf binding engine получив PropertyChanged увидит, что объект не изменился и проигнорирует обновление.
Теперь, когда решение практически готово, встает вопрос, а как без code-behind, создавать generic-классы в xaml? Ведь нам надо будет инстанцировать generic-класс
GenericObservableConverter. Тут опять не обошлось без гуру и навыручку пришел пост от Mike'а Hilberg'a о поддержке generic'ов в xaml. Для инстанцирования generic классов он предлагает использовать markup extension - GenericExtension. Вот слегка модифицированный код главного метода класса:
GenericObservableConverter. Тут опять не обошлось без гуру и навыручку пришел пост от Mike'а Hilberg'a о поддержке generic'ов в xaml. Для инстанцирования generic классов он предлагает использовать markup extension - GenericExtension. Вот слегка модифицированный код главного метода класса:
Решение в сборе выглядит следующим образом:
Недостатки данного решения: ссылаться через StaticResource на класс инстанцированный при помощи markup extension можно только из стиля в ресурсах. В других же случаях Converter необходимо будет создавать в каждом Binding.
Удачи.
1 комментарий:
мусчина я в этот раз понял прочел больше чем в прошлый но ни черта опять не понял : )
но все равно буду заходить и оставлять бесполезные комменты : )
Отправить комментарий