Полагаю, я немного застрял в механизме привязки данных WPF. До сих пор я думал об этом так: любая цель DataBinding должна быть DependencyProperty. Учитывая это, я разработал очень простой основанный на кнопках UserControl, подобный следующему (сводится к самым необходимым частям):
XAML
<Button x:Class="MyNamespace.IconButton"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="imIcon"
Source="{Binding Path=ImageSource}"
Grid.Column="0"
Width="16" Height="16"
Margin="0,0,5,0"/>
<TextBlock x:Name="tbText" Text="{Binding Path=Text}" Grid.Column="1"/>
</Grid>
</Button>
C #
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace MyNamespace
{
public partial class IconButton : Button
{
public string Text
{
get { return this.tbText.Text; }
set { this.tbText.Text = value; }
}
public ImageSource ImageSource
{
get
{
return (ImageSource)GetValue(ImageSourceProperty);
}
set
{
SetValue(ImageSourceProperty, value);
}
}
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource",typeof(ImageSource), typeof(IconButton));
public IconButton()
{
InitializeComponent();
}
}
}
Использование этого элемента управления довольно просто:
<v:IconButton Margin="5,0,0,0" Height="24" Width="100"
Text="reload"
ImageSource="..\Resources\images\refresh.png"/>
Visual Studio Designer отображает кнопку и текст, но отказывается оценивать свойство зависимости 'ImageSourceProperty'. То же самое происходит, когда я реализую 'Text' как DependencyProperty. Почему он это делает?
А также, зачем мне вообще нужен обычный ImageSource? Если я удаляю это свойство из своего кода, редактор VS XAML (где используется элемент управления) будет подсказывать свойству ImageSource свойство «System.Windows.DependencyProperty MyNamespace.IconButton.ImageSourceProperty» (вместо обычного описания свойства), что означает, что он может разрешить свойство зависимости правильно, тем не менее я не могу установить какое-либо значение таким образом («У присоединенного свойства нет установщика»). Почему я могу установить значение свойства зависимости только через обычное свойство из xaml?
Наконец, у меня есть другая проблема. Чтобы упростить объяснения, я использую настройку Article-Category в этом примере. Предполагается, что пользовательский интерфейс позволяет пользователям классифицировать товары магазина.
<ListView x:Name="lvArticles"
ItemsSource="{Binding Path=Articles}"
IsSynchronizedWithCurrentItem="True"/>
<ComboBox IsSynchronizedWithCurrentItem="True"
x:Name="cbCategories"
ItemsSource="{Binding Path=Categories}"
SelectedItem="{Binding Path=Articles.CurrentItem.Category, Mode=TwoWay}"/>
Я могу использовать это, чтобы изменить мои статьи (то есть установить новые категории), сделав выбор в cbCategories, но при выборе статьи из lvArticles cbCategories автоматически не назначается связанной категории. Что-то не так с тем, что я делаю, или это еще одна аномалия WPF?
Заранее большое спасибо за любые подсказки ...
EDIT
Со вчерашнего дня я не думал ни о чем, кроме этого странного поведения ComboBox. Первым недостатком, который я обнаружил в своем коде, было отсутствие переопределения метода Equals
в ViewModels, который вошел в ComboBox. В результате не было никаких шансов на то, чтобы что-либо было SelectedItem - все сравнения элементов ItemSource с SelectedItem приводили к аккуратному «Ложь». Я сейчас исправил это. Но в моем режиме Master-Detail-View детали все равно не будут привязываться к правильным значениям при изменении выбранной основной записи.
ЕЩЕ ДРУГОЕ РЕДАКТИРОВАНИЕ
Чтобы восполнить это, я создал очень уродливый обходной путь, основанный на событиях.
<ListView x:Name="lvArticles"
ItemsSource="{Binding Path=Articles}"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="LvArticlesSelectionChanged"/>
Основной обработчик событий выглядит следующим образом:
private void LvArticlesSelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ArticleViewModel avm = this.Articles.SelectedItem as ArticleViewModel;
foreach (CategoryViewModel cvm in this.ViewModel.Categories)
{
if( cvm.Equals( avm.Category ) )
{
this.cbCategories.SelectedItem = cvm;
}
}
}
Это самое уродливое решение, которое я могу себе представить - для чего у нас есть привязки? Но так как я должен доставить, я пойду на это, пока кто-нибудь не придет с великим объяснением ...