Я изучаю механизм прикрепленного поведения Silverlight, чтобы использовать шаблон Model-View-ViewModel в моих приложениях Silverlight. Для начала я пытаюсь получить простой Hello World, но я полностью застрял в ошибке, для которой я не могу найти решение.
Теперь у меня есть страница, которая содержит только кнопку, которая должна отображать сообщение при нажатии. Событие click обрабатывается с использованием класса, полученного из Behavior, и сообщение указывается как свойство зависимости самого поведения. Проблема возникает при попытке привязать свойство message к свойству класса viewmodel, используемого в качестве контекста данных: я получаю exeption в вызове InitializeComponent
в представлении.
Вот весь код, который я использую, поскольку вы можете видеть, что это довольно просто. Сначала разметка главной страницы и ее вида:
MyPage
<UserControl x:Class="MyExample.MyPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyExample"
>
<Grid x:Name="LayoutRoot">
<local:MyView/>
</Grid>
</UserControl>
MyView (в текстовом блоке есть только проверка правильности синтаксиса привязки)
<UserControl x:Class="MyExample.MyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MyExample"
Width="400" Height="300">
<StackPanel Orientation="Vertical" x:Name="LayoutRoot" Background="White">
<StackPanel.Resources>
<local:MyViewmodel x:Key="MyResource"/>
</StackPanel.Resources>
<TextBlock Text="This button will display the following message:"/>
<TextBlock Text="{Binding MyMessage, Source={StaticResource MyResource}}" FontStyle="Italic"/>
<Button x:Name="MyButton" Content="Click me!">
<i:Interaction.Behaviors>
<local:MyBehavior Message="{Binding MyMessage, Source={StaticResource MyResource}}"/>
</i:Interaction.Behaviors>
</Button>
</StackPanel>
</UserControl>
Теперь код, есть два класса: один для поведения и еще один для viewmodel:
MyViewmodel
public class MyViewmodel
{
public string MyMessage
{
get { return "Hello, world!"; }
}
}
MyBehavior
public class MyBehavior : Behavior<Button>
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message",
typeof(string), typeof(MyBehavior),
new PropertyMetadata("(no message)"));
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += new RoutedEventHandler(AssociatedObject_Click);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Click -= new RoutedEventHandler(AssociatedObject_Click);
}
void AssociatedObject_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Message);
}
}
Достаточно просто, но этот код генерирует AG_E_PARSER_BAD_PROPERTY_VALUE [Line: 15 Position: 43]
(прямо в начале значения, заданного для свойства Message) при запуске. Я уверен, что я что-то упустил, но что?
Дополнительная информация: если я удалю привязку из свойства Message на MyBehavior (то есть, если я установил его значение для любой статической строки), он отлично работает. Кроме того, я нацелен на silverlight 3 RTW.
Спасибо большое!
UPDATE
Похоже, что в отличие от WPF, Silverlight не поддерживает привязку данных к любому объекту, происходящему из DependencyObject, а только к объектам, происходящим из FrameworkElement. Это не относится к Поведению, поэтому привязка не работает.
Я нашел обходное решение здесь, в виде чего-то названного суррогатным связующим. В основном вы указываете связанный элемент и свойство, а также значение как атрибуты элемента FrameworkElement, содержащего объект non-FrameworkElement.
ОБНОВЛЕНИЕ 2
Суррогатное связующее не работает, когда FrameworkElement содержит подэлемент Interaction.Behaviors.
Я нашел другое решение здесь, и этот, похоже, работает. На этот раз используется трюк DeepSetter. Вы определяете один из таких сеттеров как статический ресурс на содержащем StackPanel, а затем ссылаетесь на ресурс из поведения. Поэтому в моем примере мы должны развернуть раздел ресурсов StackPanel следующим образом:
<StackPanel.Resources>
<local:MyViewmodel x:Key="MyResource"/>
<local:DeepSetter
x:Key="MyBehaviorSetter"
TargetProperty="Message"
BindingExport="{Binding MyMessage, Source={StaticResource MyResource}}"/>
</StackPanel.Resources>
... и измените объявление поведения кнопки следующим образом:
<local:MyBehavior local:DeepSetter.BindingImport="{StaticResource MyBehaviorSetter}"/>
ОБНОВЛЕНИЕ 3
Хорошие новости: привязка данных для любого DependecyObject будет доступна в Silverlight 4: http://timheuer.com/blog/archive/2009/11/18/whats-new-in-silverlight-4-complete-guide-new-features.aspx#dobind