views:

54

answers:

1

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...

A: 

You bind the DataSource to the Button, not to your IconButton:

DataContext="{Binding RelativeSource={RelativeSource Self}}"

My guess is that your wish to bind to your original parent?

Two options:

Option 1 [change RelativeSource binding]:

Most simple solution:

Change the DataSource thing to find your correct parent.

DataContext="{Binding RelativeSource={RelativeSource FindAncestor, 
                     AncestorType={x:Type MyNamespace:IconButton}}}"

Option 2 [Template bindings]:

Rewrite your control, so that it uses a ControlTemplate, so you can use TemplateBindings on the properties you want to take over from your parent control:

<Button x:Class="MyNamespace.IconButton">
  <Button.Template>
      <ControlTemplate TargetType="MyNamespace:IconButton">
          <Grid>
        ...
        <Image x:Name="imIcon"
               Source="{TemplateBinding ImageSource}" 
               Grid.Column="0" 
               Width="16" Height="16"
               Margin="0,0,5,0"/>
        <TextBlock x:Name="tbText" Text="{TemplateBinding Text}" Grid.Column="1"/>
          </Grid>
      </ControlTemplate>
  <Button.Template>
</Button>
Arcturus
Setting the DataContext in XAML was my second attempt. At first I used the IconButton's Constructor for that (this.DataContext = this). Going back there is the easiest fix I can Imagine...but it doesn't change a thing.
Sebastian Edelmeier
Don't rely on the Visual Designer too much. It usually is the first thing to break in our projects. If it works with compiled code in runtime, it is alright, not? :)
Arcturus
Yup, it's just an auxiliary tool anyway. BTW, thanks for your response! I am afraid it won't take me very far,since it moves complexity to the template, but doesn't eliminate / explain my problem
Sebastian Edelmeier