Использование поведений
Для тестирования созданного поведения понадобится создать новый проект WPF-приложения, добавить в него ссылку на библиотеку классов, в которой определен класс DragInCanvasBehavior (созданный в предыдущей статье), и сборку System.Windows.Interactivity.dll. Затем нужно отобразить оба пространства имен в XML-разметке. Предполагая, что класс DragInCanvasBehavior содержится в библиотеке классов CustomBehaviorsLibrary, необходимый код разметки выглядит следующим образом:
Чтобы использовать это поведение, его понадобится добавить в любой элемент внутри Canvas с помощью присоединяемого свойства Interaction.Behaviors. Приведенная ниже разметка создает контейнер Canvas с тремя фигурами. Два элемента Ellipse используют поведение DragInCanvasBehavior и потому могут перетаскиваться внутри Canvas, а элемент Rectangle это поведение не использует и, соответственно, перемещаться в Canvas не может:
Но это еще не все. Если вы разрабатываете приложения в Expression Blend, то поведения предоставят даже еще более удобные возможности на этапе проектирования, одной из которых является возможность вообще избавиться от написания разметки.
Поддержка использования поведений в Expression Blend
В Expression Blend работа с поведениями сводится к операциям перетаскивания и конфигурирования. Первым делом необходимо позаботиться, чтобы в приложении присутствовала ссылка на сборку, в которой содержатся используемые поведения. (В рассматриваемом случае это сборка библиотеки классов, в которой определен класс DraglnCanvasBehavior.) Кроме того, должна присутствовать ссылка на сборку System.Windows.Interactivity.dll.
Среда Expression Blend автоматически ищет поведения во всех указанных сборках и отображает их в панели Asset Library (Библиотека ресурсов); эта же панель используется для выбора элементов при проектировании страницы Silverlight. Также Expression Blend добавляет поведения из сборки Expression.Interactions.dll производства Microsoft, даже если в проекте не было на нее явной ссылки.
Чтобы просмотреть доступные поведения, нарисуйте кнопку на поверхности проектирования страницы, щелкните на кнопке Asset Library и перейдите на вкладку Behaviors (Поведения), как показано на рисунке:
Чтобы добавить действие к элементу управления, перетащите его из Asset Library на нужный элемент управления (в рассматриваемом примере — на одну из фигур в Canvas). После этого Expression Blend автоматически создаст поведение, которое впоследствии можно конфигурировать (при наличии у него свойств).
How to add System.Windows.Interactivity to project?
My project missing System.Windows.Interactivity . Google says that I have to install Expression Blend, but on my other computer I have this library and I don’t have Expression Blend installed. So there should be another way to obtain System.Windows.Interactivity ? What should I do? (right now i don’t have another computer so I can not just copy this library 🙂
11 Answers 11
It used to be in the Blend SDK (deprecated).
See Jan’s answer for more details if you need to migrate.
Although this issue is quite old, i think this is relevant news / the most recent answer: Microsoft open-sourced XAML Behaviours and posted a blog post how to update to this version: https://devblogs.microsoft.com/dotnet/open-sourcing-xaml-behaviors-for-wpf/
To save you a click, this is the main steps to migrate:
- Remove reference to “Microsoft.Expression.Interactions” and “System.Windows.Interactivity”
- Install the Microsoft.Xaml.Behaviors.Wpf NuGet package.
- XAML files – replace the xmlns namespaces http://schemas.microsoft.com/expression/2010/interactivity and http://schemas.microsoft.com/expression/2010/interactions with http://schemas.microsoft.com/xaml/behaviors
- C# files – replace the usings in c# files “Microsoft.Xaml.Interactivity” and “Microsoft.Xaml.Interactions” with “Microsoft.Xaml.Behaviors”
There is a new NuGet package that contains the System.Windows.Interactivity.dll that is compatible with:
- WPF 4.0, 4.5
- Silverligt 4.0, 5.0
- Windows Phone 7.1, 8.0
- Windows Store 8, 8.1
To install Expression.Blend.Sdk, run the following command in the Package Manager Console
The easiest way might be to get it from NuGet:
With Blend for Visual Studio, which is included in Visual Studio starting with version 2013, you can find the DLL in the following folder:
You will have to add the reference to the System.Windows.Interactivity.dll yourself though, unless you use Blend for Visual Studio with an existing project to add functionality that makes use of the Interactivity namespace. In that case, Blend will add the reference automatically.
Sometimes, when you add a new library, in introduces a clashing version of System.Windows.Interactivity.dll .
For example, the NuGet package MVVM light might require v4.2 of System.Windows.Interactivity.dll , but the NuGet package Rx-XAML might require v4.5 of System.Windows.Interactivity.dll . This will prevent the the project from working, because no matter which version of System.Windows.Interactivity.dll you include, one of the libraries will refuse to compile.
To fix, add an Assembly Binding Redirect by editing your app.config to look something like this:
Don’t worry about changing the PublicKeyToken , that’s constant across all versions, as it depends on the name of the .dll, not the version.
Ensure that you match the newVersion in your appConfig to the actual version that you end up pointing at:
Alternative solution is to modify your current Visual Studio installation in the Visual Studio Installer
Win + R %ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vs_installer.exe
adding the Blend for Visual Studio SDK for .NET ‘Individual component’ under ‘SDKs, libraries, and frameworks’:
after adding this component System.Windows.Interactivity should appear in its regular location Add Reference/Assemblies/Extensions .
It appears this would only work for VS2017 or earlier. For later versions, please refer to other answers.
I have had the exact same problem with a solution, that System.Windows.Interactivity was required for one of the project in Visual Studio 2019, and I tried to install Blend for Visual Studio SDK for .NET from Visual Studio 2019 Individual components, but it did not exist in it.
The consequence of that, I was not able to build the project in my solution with repetitive of following similar error on different XAML parts of the project:
The tag ‘Interaction.Behaviors’ does not exist in XML namespace ‘clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity’.
The above mentioned errors snapshot example
The solution, the way I solved it, is by installing Microsoft Expression Blend Software Development Kit (SDK) for .NET 4 from Microsoft.
Thanks to my colleague @felza, mentioned that System.Windows.Interactivity requires this sdk, that is suppose to be located in this folder:
In my case it was not installed. I have had this folder C:\Program Files (x86)\Microsoft SDKs with out Expression\Blend\.NETFramework\v4.0 folder inside it.
After installing it, all errors disappeared.
WPF: Что делать, когда свойство не поддерживает привязку
Введение
WPF — замечательная технология, которую, не смотря на все ее недостатки, очень люблю. Тем не менее, часто приходится писать не разметку, а код, который помогает первой работать как надо. Хотелось бы этого избегать и писать чистый XAML, но до сих пор ни одно мое приложение сложнее простого не обходилось без различных хелперов (классов-помощников), написанных на C#. К счастью, есть распространенные случаи, где можно одним хелпером решить сразу группу проблем.
Речь ниже пойдет о привязке в обычных свойствах визуальных элементов, которые не являются свойствами зависимостей (dependency properties). Штатными средствами WPF этого сделать не получится. Ко всему прочему, мы не можем узнать об изменениях такого свойства, кроме как подписавшись на специальное событие, что противоречит шаблону MVVM. Такие события для каждого свойства могут быть свои. Самый распространенный пример — это PasswordBox и его свойство Password. Так у нас сделать не получится:
Не будем вдаваться в подробности, зачем разработчики PasswordBox не разрешили привязываться к свойству пароля. Подумаем, что тут можно сделать.
Если вам не хочется читать мои рассуждения, то в конце статьи опубликован финальный код и ссылка на Github.
Определяемся со способом реализации
И так! Хотелось бы такое решение, чтобы в обращении походило на обычную привязку, но с некоторыми дополнительными параметрами. Также, неплохо иметь возможность делать привязку в обе стороны (two way binding). Для реализации перечисленного хелперу понадобятся три входных параметра:
- Свойство визуального элемента
- Событие, сообщающее об изменениях
- Источник данных для привязки
К примеру, для PasswordBox это будут соответственно: свойство Password, событие PasswordChanged и источник OtherProperty.
Есть разные способы достичь желаемого. Остановимся на стандартном для таких случаев механизме — поведениях.
Поведение (behavior) — это класс, который добавляет визуальному элементу дополнительную функциональность. Есть два вида поведений: статические и наследуемые от класса Behavior . Описание их различий выходит за рамки статьи. Я выбрал второй вариант, как обладающий большими возможностями.
Пишем код
Добавим ссылку на сборку System.Windows.Interactivity.dll. Это часть SDK редактора Expression Blend и находится в разделе «Расширения» окна выбора сборок Visual Studio.
Создадим класс, наследуемый от Behavior :
Тип генерика DependencyObject выбран как наиболее общий. Ведь мы пишем универсальный класс, подходящий для любого элемента, а не только PasswordBox.
Алгоритм работы будет краток. Для привязки от свойства к источнику:
- Подписываемся на событие об изменении свойства визуального элемента.
- В обработчике записываем обновленное значение в источник.
Для обратной привязки:
- Через привязку определяем момент изменения значения источника.
- Записываем обновленное значение в свойство визуального элемента.
Для вышеописанных входных параметров создадим три свойства:
- Property — имя свойства визуального элемента, не поддерживающее привязку;
- UpdateEvent — имя события, уведомляющего об изменении значения нашего свойства;
- Binding — свойство зависимостей для привязки к источнику данных. Позволяет применять знакомый механизм привязки.
В данном случае свойства Property и UpdateEvent являются обычными, этого достаточно. Свойство Binding, напротив, должно быть свойством зависимостей, ведь именно сюда подключается источник данных.
Теперь, когда у нас есть все входные данные, приступим к их обработке в переопределенном методе OnAttached(). Он вызывается при присоединении поведения к визуальному элементу. К последнему можно обращаться через свойство класса AssociatedObject. В противовес, при отсоединении вызывается OnDetaching().
Нам нужны объекты для работы со свойством визуального элемента и событием через рефлексию. Ниже показано их получение и подписывание на событие, уведомляющее об изменениях свойства визуального элемента.
Далее в примерах я опустил различные проверки на null, чтобы не засорять внимание. В итоговом варианте класса они присутствуют.
В коде выше имеется метод CreateDelegateForEvent(). Он компилирует объект делегата для указанного события во время выполнения. Ведь заранее мы не знаем сигнатуру обработчика события. При компиляции в делегат помещается вызов метода action, которым в нашем случае является EventFired(). В нем мы будем выполнять нужные нам действия для обновления значения источника данных.
Эта операция довольно ресурсоемка, но выполняется лишь раз, при подключении поведения. Ее можно оптимизировать жертвуя гибкостью, предположив, что события могут быть только RoutedEvent. Тогда, вместо дорогой компиляции, достаточно подписаться на событие с указанием обработчика EventFired(), предварительно изменив ему сигнатуру на совместимую с RoutedEventHandler. Но оставим здесь оригинальный вариант. Преждевременная оптимизация — зло.
Метод EventFired() предельно прост, он записывает новое значение в источник данных:
Осталась самая малость — менять значение свойства визуального элемента при изменении источника данных. Для этого подойдет переопределяемый метод OnPropertyChanged(), который сообщает об изменениях свойств зависимостей класса. Так как при изменении источника данных меняется и свойство Binding, то нам достаточно отслеживать его новые значения.
Все, вроде бы, прекрасно. Мы задаем новое значение свойства визуального элемента и… получаем StackOverflowException.
Проблема в том, что при изменении свойства автоматом вызывается уведомляющее событие, на которое мы подписаны. В событии изменяется значение источника, а при изменении источника меняется свойство Binding, что приводит нас снова в метод OnPropertyChanged(). Рекурсия.
Самым простым решением будет добавление сравнения старого и нового значения свойства:
Здесь делаем допущение, что Equals() у типа реализован как надо и не будет вечно возвращать false.
Наш хелпер готов!
Результат
В данном случае TextBox и PasswordBox будут синхронно менять значения.
Заключение
Мы добились работы привязки для свойства визуального элемента, которое изначально ее не поддерживало. Привязка работает в оба направления и можно использовать класс поведения для любого элемента, не беспокоясь о различиях в именах свойств и событий.
Заранее извиняюсь за неточности в тексте, моя первая статья.
Как и обещал, финальный код: