Hi all,
I guess I'm a bit stuck on the mechanism of WPF DataBinding. Up to now I've thought of it as this : Any target of a DataBinding has to be a DependencyProperty.Keeping that in mind, I designed a very simple button-based UserControl like the following (boiled down to the most necessary parts):
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();
}
}
}
Using this Control is fairly straightforward:
<v:IconButton Margin="5,0,0,0" Height="24" Width="100"
Text="reload"
ImageSource="..\Resources\images\refresh.png"/>
The Visual Studio Designer displays the button and the text, but refuses to evaluate the dependency property 'ImageSourceProperty'. The same happens when I implement 'Text' as a DependencyProperty. Why does it do this?
And also, why do I need a regular 'ImageSource' at all? If I remove this Property from my code, the VS XAML Editor (where using the control) will Tooltip the ImageSource Property with "System.Windows.DependencyProperty MyNamespace.IconButton.ImageSourceProperty" (instead of the regular property description), which means it can resolve the dependency property correctly, nevertheless I can't set any value this way ("Attached Property has no setter"). Why can I only set the value of a dependecy property via a regular property from xaml?
Finally, I have another problem. To simplify explanations, I use an Article-Category setup in this example. The UI is supposed to enable users to recategorize shop articles.
<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}"/>
I can use this to alter my articles (i.e. set new categories) by making a selection in cbCategories, but when selecting an article from lvArticles, cbCategories is not automatically set to the related Category. Is there anything wrong with what I'm doing or is this yet another WPF anomaly?
Thanks a lot in advance for any hints...
EDIT
Since yesterday, I haven't been thinking about anything but this weird ComboBox behavior. The first flaw I found in my code was the missing override of the Equals
method in the ViewModels that went into the ComboBox. As a result, there was no chance for anything ever being the SelectedItem - all comparisons of the ItemSource's elements with the SelectedItem resulted in a neat 'False'. I've now patched this up. But in my Master-Detail-View, the Details still won't snap to the correct values when changing the selected Master Record.
YET ANOTHER EDIT
To make up for this I have created a very ugly event-driven workaround.
<ListView x:Name="lvArticles"
ItemsSource="{Binding Path=Articles}"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="LvArticlesSelectionChanged"/>
The underlying event handler looks like the following :
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;
}
}
}
This is the most ugly solution I can imagine - what do we have bindings for? But since I have to deliver, I'll go for this until somebody comes along with a great explanation...