Comments 44
Основная проблема в том то, что вы в статику пытаетесь засунуть динамику. Отсюда и вопросы с производительностью, отладкой, null-значениями и т.д. А так, получается красивый велосипед, который я никогда использовать не буду…
Поясните, пожалуйста, подробнее что Вы имеете в виду под «вы в статику пытаетесь засунуть динамику». Производительность такого биндинга такая же как если бы вы реализовали биндинг с использованием конвертера, поскольку само выражение преобразовывается в анонимную функцию один раз
XAML — это статика и изначально не предназначен для логики (триггеры и т.д. не в счет).
«Производительность такого биндинга такая же как если бы вы реализовали биндинг с использованием конвертера» — вы замеряли? Конвертер реализован при помощи интерфейса, он не парсит строки, не преобразовывает их в выражения, поэтому в вашем случае очевидно, что производительность будет меньше.
да, замерял. Конвертер парсит строку 1 раз, по строке создается делегат, который ничего уже не парсит, а выглядит как функция, принимающая на вход source property и реализующая логику распарсенного выражения. Замер скорости ведется для всех биндингов из примера к библиотеке, которая лежит на github рядом с ним.
я не говорю о том, что в xaml следует переносить любую хоть немного сложную логику: операции с массивами, linq выражения, вызов функций из своих классов и т.д. но я считаю что простые выражения, которыми активно можно пользоваться в интерфейсе, могут и должны быть очевидным образом прописаны в xaml.
В CalcBinding нельзя задать свой собственный конвертер. Я не придумал сценария, в котором требовалась бы такая возможность, поэтому если у вас есть предложения, буду рад их прочитать.
Есть два поля ввода. В одном — http://example.com, в другом — pic.jpg. Нужно собрать путь из двух частей и показать картинку. С помощью CalcBinding решаемо?
Спасибо, хороший пример! На первый взгляд решение без конвертера не нашлось
Добавить поддержку конвертера не составляет труда, единственной причиной ее отсутствия было отсутствие примера который бы показывал необходимость в этом. О различиях между библиотеками напишу в ответе на ваш второй комментарий, спасибо.
Вы чемпион в дисциплине — «Что мне сделать чтобы не писать легко читаемый и трестируемый код во вью модели?»
В техническом плане — то что вы сделали — просто супер. Но я бы никогда не стал использовать это в продакшене. Все приведенный use-case намного проще (более читаемо и проще для тестирования) имплементировать на уровне view-model.
Но повторюсь — с технической точки зрения — просто прекрасно.
А вот такой кейс: ViewModel содержит высоту ячейки таблицы RowHeight. Каждая ячейка оборачивается в Border, у которого Thickness рассчитывается как (0, 0, ViewModel.RowHeight / 30.0, 0). Как это сделать красивее? Ввести свойство BorderThickness во вью-модели? Написать конвертер? На мой взгляд, предложенное в статье решение в данном случае было бы оптимальным, и для меня остается загадкой, почему разработчики WPF не предоставили такой возможности out-of-the-box.
а просто показывать Thickness из view-model чем вам не нравится?
Как плюс — явная прозрачность алгоритма получения, отличная тестируемость (никакой разработчик вам никогда не сломает ваш алгоритм), возможность пробежать по крайним случаям (а что будет если например ViewModel.RowHeight = 0 ?).
Конвертор — это будет отдельный класс — согласен что для простой математической операции — оверкил, но при этом его тоже на порядок легче сопровождать.
ИМХО — xaml и так очень сложно читаемый язык программирования. Вынося в него логику — вы усложняете себе жизнь в дальнейшем.
Не соглашусь с Вами, что «более читаемо и проще имплементировать туже самую логику на уровне view-model» или конвертера. Конвертер выглядит сложнее и пишется дольше, а использование свойств для этих целей во вьюмодели загромождают её.

А что вы имеете в виду под сопровождением конвертера? Часто ли вы сопровождаете InverseBooleanConverter? Какие сложности сопровождения этого решения Вы видите кроме опасения того, что это сторонняя библиотека?
Конвертор как раз сопровождать просто (зафиксировать функциональность и написать тесты) так-же как и view-model. Сложно сопровождать xaml — а именно код в нем написанный.
Проблема в том что вы часть кода переносите в xaml — имхо порочная практика.

> а использование свойств для этих целей во вьюмодели загромождают её.

Имхо — нет. Ответственность view-model — это содержать логику работы view. Запрос веб странички — загромождает view-model, а вот как для этого ui-элемента конвертировать состояние в визуальное представление — нет.

Опять — же все сказанное ИМХО, наработанное за несколько лет ведения и развития нескольких длинных проектов на WPF (все проекты живут уже по 2-3 года и активно развивались без переписывания с нуля)
Аргумент против экспонирования толщины бордера во View-Model:
Изначальная задумка разработчиков WPF была в том, что XAML-представление должен рисовать дизайнер, а code-behind пишет разработчик. View-Model должна содержать данные для отображения, но не стилистические особенности элементов. В моём примере ширина бордера является 1/30 высоты ячейки, но в какой-то момент дизайнер может захотеть изменить это значение, и в этом случае ему придётся идти к программисту или самому лезть в код модели.
Конечно, концепция разделения зон ответственности дизайнеров и программистов с треском провалилась (по крайней мере, я не знаю ни одного живого примера), но к этому следует стремиться.
Согласен с вами. Ни на одном из моих мест работы я не увидел ни одного дизайнера, занимающегося разработкой интерфейса на wpf, все делает программист. А программисты очень не любят писать что то однообразное длинное и повторяющееся
>В моём примере ширина бордера является 1/30 высоты ячейки, но в какой-то момент дизайнер может захотеть изменить это значение, и в этом случае ему придётся идти к программисту или самому лезть в код модели.
>Конечно, концепция разделения зон ответственности дизайнеров и программистов с треском провалилась (по крайней мере, я не знаю ни одного живого примера), но к этому следует стремиться.

А зачем? Это не работает. Больше того — отдать xaml дизайнеру это получить либо проблемы в производительности, либо проблемы с разъезжающимся UI. ИМХО — с этим надо просто смирится и жить дальше. Уже сделали что дизайнер может разговаривать с разработчиком на одном языке (xaml), но допускать любого кроме разработчика к мастер ветке — это верный путь к багам.

>View-Model должна содержать данные для отображения

ИМХО но данным место в модели. Если у нас хранится высота колонки во вью модели, то и ширину ее бордера имеет смысл хранить и считать там-же (все в одном месте как минимум). View-Model это модель вьюшки и вся кастомная логика вью должна быть там.
>> Больше того — отдать xaml дизайнеру это получить либо проблемы в производительности, либо проблемы с разъезжающимся UI
Ну, это уж вопрос профессионализма дизайнера…
Это вопрос сферичности дизайнера в вакууме. Много вы знаете дизайнеров UI, которые понимают все нюансы XAML?
Я, честно говоря, вообще не встречал в реальной жизни команд с UI-дизайнерами, т.к. мне приходится разрабатывать приложения, где заказчику важен функционал, а не рюшечки. Всё рисуем своими силами. Но надеюсь, такие команды всё же есть. И в моём понимании, если дизайнер работает в команде, использующей WPF, он должен не просто формочки в фотошопе клепать, а предоставлять на выходе полноценный макет на XAML-е, удовлетворяющий всем требованиям заказчика. И если по дизайну было требование, что ширина бордера должна быть 1/30 высоты ячейчи, то это задача не программиста, а именно дизайнера, на мой взгляд. Программист может даже не знать, в каком виде должны отображаться его данные — может, это вообще не таблица, а круговая диаграмма. Именно поэтому я считаю, что ViewModel не следует перегружать информацией о способе представления.
Насколько мне известно, таких дизайнеров сложно найти, и стоят они очень дорого, не каждая компания может себе позволить себе такое удовольствие. Если нашёл дизайнера, который хорошо разбирается в юзабилити, а не только раскрашивает картинки в фотошопе — уже радость.

Вообще, при всём разделении между логикой и представлением всё равно они тесно связаны, и логики во вьюхах до черта: триггеры, конвертеры, селекторы шаблонов, привязка к моделям и т.п. Я как программист лучше буду иметь свободу рефакторить код и держать его в чистоте, чем пускать в XAML дизайнера.
Заставлять дизайнера ковыряться в XAML — примерно как заставлять его верстать HTML. То есть, крайне неэффективное применение.
В мире веб-разработки есть такая специализация — верстальщик. Поэтому там дизайнеры и программисты освобождены от этой рутины. (В отсутствии верстальщика этим, как правило, всё же занимается дизайнер).

В мире WPF такого выделенного человека нет, поэтому этим приходится заниматься программистам, ибо редкий дизайнер осилит.
Нет — вопрос профессионализма дизайна — это удобство тех интерфейсов которые он дизайнит — это все. Дизайнер не должен понимать что 2 лишних бордера в коллекции элементов, где смена элементов происходит часто — это проблема производительности. Это задача разработчика. И отвечает за это разработчик. И то что приложение может расползтись при разных DPI в системе — тоже забота разработчика. К дизайнеру это не имеет никакого отношения.
В данном случае за вас в общем виде уже написан, протестирован и готов к использованию конвертер, решающий определенный круг задач в общем виде. Выражение в binding выходит не сложнее того, что позволяет ввести обычный инженерный калькулятор. Если же вы в нем делаете ошибку, то у вас в output появится соответствующее сообщение в стиле binding errors. Если у вас все заработало, то тест пройден и это просто работает. Ошибки, которые вы можете поймать в дальнейшем, могут быть связаны с невалидными или отсутствующими данными, тогда вы увидите теже самые ошибки в output, что и при использовании стандартного binding.

Сложность в сопровождении составляет большой по объему код со сложной запутанной логикой, в данном же случае мы видим обратную ситуацию, кода мало он в одном месте, он выглядит очень просто, значит менять его не ожидая что где то, что то сломается можно без опасений
Да, действительно. Из-за таких примеров, а также BoolToVisibility и InverseBool конвертеров и было предложено решение
В кросс-платформенной библиотеке MVVMCross для Xamarin Стюарт (автор) реализовал простой и понятный биндинг который он назвал Tibet:

Using these ideas, then a binding like:
  Text TweetText, 
     Converter=RemainingLength,
     ConverterParameter=140

might be rewritten:
  Text RemainingLength(TweetText,140)

or perhaps even:
  Text 140 - Length(TweetText)

а также особо полезные ValueCombiners типа IIF, Format >>>

Он так же предлагает реализацию Tibet для XAML в виде AttachedProperties
<TextBlock mvx:Bi.nd="Text Customer.FirstName; Visible=ShowFirstName" />


Интересно, почему не в виде MarkupExtension как в этом примере
Мне больше всего нравится подход reactiveui с биндингами в code behind.
Проверка корректности на этапе компиляции, интеллисенс, отсутствие необходимости написания конвертеров.
Красивое решение. Очень хорошо подойдёт для каких-нибудь небольших проектов, которые нужно побыстрее и попроще сделать. Но, присоединюсь к bessony и onikiychuka, в большом продакшн-проекте я бы опасался использовать такой подход.
Принципиально отличается двумя пунктами.
Первое, quick converter судя по всему не умеет вынимать из выражения path имена source property. Поэтому они используют простые стандартные шаблоны $p которые без труда можно найти. Из за этого вам приходится лепить эти конструкции с шаблонами а потом еще биндинги отдельно указывать, согласитесь выглядит не очень юзабельно. Я бы не хотел этим загромождать xaml, для меня, как автора либы, вопрос удобства использования стоял принципиально. В моем биндинге в path вы можете писать какие угодно свойства и вложенные в несколько других вью моделей и понескольку раз одно и тоже, словом как вам удобно вытащить данные. А вот свойства типа p0 p1 извините, выглядят как костыль или временное решение

Второе. В quick converter необходимо вручную забивать выражение для обратного биндинга из dependency property к source property. У меня, как описание в статье выше, выражение представляется в виде функции для которой автоматически ищется обратная и новое выражение служит для обратного связывания

Насчет плюсов quick converter: я считаю что уже Linq выражения это уж точно не обязанность представления, это довольно далеко от представления и это должно быть во вьюмодели, кстати это тот случай о котором писал onikiuchika, писать tostring с целью поменять формат выода в path тоже не следует, для этого есть прекрасное свойство string format в стандартном binding.

С учетом этого я не вижу сильных преимуществ quick converter перед моим решением.

Словом, мое решение требует от вас меньше кода, а возможностей, которыми вы реально будете пользоваться, дает больше
Первое, quick converter судя по всему не умеет вынимать из выражения path имена source property. Поэтому они используют простые стандартные шаблоны $p которые без труда можно найти. Из за этого вам приходится лепить эти конструкции с шаблонами а потом еще биндинги отдельно указывать, согласитесь выглядит не очень юзабельно. Я бы не хотел этим загромождать xaml, для меня, как автора либы, вопрос удобства использования стоял принципиально
Маленький ньюанс. Указание параметров через P0 выдерживает решарперовский Rename, а ваше решение — нет. Таким образом, XAML с использованием вашей либы становится неподдерживаемым средствами автоматического рефакторинга. Решение о том, что юзать в продакшн-проекте, думаю, очевидно.
Стоящее замечание, спасибо. То есть если я вас правильно понял решарпер в случае переименования свойства во вьюмодели изменит в xaml все биндинги к нему. А как он определит в design time что вьюха относится к определенной вьюмодели? Только по name convention?
<Window x:Class="WpfSandbox.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:wpfSandbox="clr-namespace:WpfSandbox"
    d:DataContext="{d:DesignInstance Type=wpfSandbox:Mdl, IsDesignTimeCreatable=True}"
    mc:Ignorable="d">

Вот так примерно делаете, после чего как студия, так и решарпер начинают понимать, какая к вьюхе привязана модель. Ну и дальше вниз по дереву уже сами выводят, если вы к какому-то свойству привязались.
Я правильно понял, что лямбда кэшируется в инстансе биндинга? Тогда возникает вопрос, что будет с производительностью, если в списке 50 элементов, и в шаблоне элемента пять вычисляемых биндингов.
Кешируется в инстансе конвертера. Объекты Binding и CalcConverter создадутся по одному разу для каждого вычисляемого биндинга, один раз скомпилируется выражение для каждого вычисляемого биндинга. И всё) А вы знаете как работают биндинги в списковых контролах типа DataGrid или ListView? У Вас и обычные биндинги тоже создадутся ровно столько раз, сколько они упомянуты в разметке, независимо от числа элементов.
Сейчас занимался сравнением производительности и для меня это тоже стало открытием, статей по поводу внутренней реализации не нашёл, остаётся только посмотреть, что внутри у этих контролов

Компиляция выражений — не самая шустрая процедура, вроде. Отражения медленнее, чем скомпилированное выражение, но из-за компиляции нужно немало вызовов, чтобы оно "окупилось".


Впрочем, без замеров не сказать, какие последствия будут на производительности.

По поводу того, что «Всякий раз… необходимо писать свой конвертер», всё уже написано до нас github.com/kentcb/WPFConverters. Поддерживаются и арифметические операции, и булевые, и много чего еще.
Да, скоро постараюсь выложить nuget пакет для .NET 4.0. Эта версия будет отличаться от версии для .NET 4.5 только отсутствием свойства ValidatesOnNotifyDataErrors, поскольку его добавили только в версии 4.5
Only those users with full accounts are able to leave comments. Log in, please.