views:

94

answers:

1

In the following XAML UserControl I am binding a few items to properties in the UserControl's linked class.

<UserControl x:Class="Kiosk.EventSelectButton"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Kiosk"
    Height="130" Width="130">
    <Grid>
        <Button 
            Style="{DynamicResource DarkButton130x130}" 
            HorizontalAlignment="Left" 
            VerticalAlignment="Center">
            <Grid Margin="0,0,0,0" Height="118" Width="118">
                <Image VerticalAlignment="Top" HorizontalAlignment="Center" Source="image/select_button_arrows.png" />
                <Image x:Name="EventImageComponent" VerticalAlignment="Center" HorizontalAlignment="Center" Effect="{DynamicResource KioskStandardDropShadow}" Source="{Binding Path=EventImage}" />
                <TextBlock x:Name="SelectTextBlock" Text="{Binding Path=SelectText}" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0,-2,0,0" FontSize="10pt" Foreground="#5aaff5" />
                <TextBlock x:Name="LabelTextBlock" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="0,0,0,0" FontSize="14pt" FontWeight="Bold" Text="{Binding Path=Label}"/>
            </Grid>
        </Button>
    </Grid>
</UserControl>

In the linked class' contstructor I'm applying the DataContext of the items to this, as you can see below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Kiosk
{
    /// <summary>
    /// Interaction logic for EventSelectButton.xaml
    /// </summary>
    public partial class EventSelectButton : UserControl
    {

        public String ValueContainer;

        private String _EventImage;

        public String EventImage
        {
            get
            {
                return _EventImage;
            }
            set
            {
                _EventImage = value;
            }
        }

        private String _Label;

        public String Label
        {
            get
            {
                return _Label;
            }
            set
            {
                _Label = value;
            }
        }

        private String _SelectText;

        public String SelectText 
        {
            get 
            { 
                return _SelectText; 
            }
            set 
            { 
                _SelectText = value; 
            }
        }

        public EventSelectButton()
        {
            InitializeComponent();
            LabelTextBlock.DataContext = this;
            SelectTextBlock.DataContext = this;
            EventImageComponent.DataContext = this;
        }
    }
}

Edit

Although this works as intended, I'm interested to know if there is a simpler way of doing this. (edit, lessons learned.) This won't actually work beyond the initialisation, the public properties will be set, however because the class doesn't use DependentProperties or alternatively, implement INotifyPropertyChanged, binding will not work as expected. (end edit)

For example,

  • Can I set the DataContext of these items in the XAML to this (as the EventSelectButton instance), and if so, how?
  • Alternatively, is it possible to inherit the DataContext from the UserControl parent, thus making the Binding Paths simpler.

The only alternatives I've found so far are more verbose, e.g. using the RelativeSource binding method to locate the EventSelectButton Ancestor.

So please, let me know any ways I can improve this binding expression, and any comments on best practices for binding within a UserComponent are much appreciated.

+1  A: 

One way is to do the following:

  1. Name your UserControl in your XAML.
  2. Bind the DataContext of the root element (i.e. Grid) to the UserControl.

Like this:

<UserControl x:Name="uc">
  <Grid DataContext="{Binding ElementName=uc}">
  .
  .
  .
  </Grid>
</UserControl>

Now, you'll ask, why not just set the DataContext of the UserControl itself? Well, this just ensures that setting the DataContext of an instance of the UserControl will still work without affecting the bindings in the UserControl's visual tree. So something like the one below will still work fine.

<Window>
  <uc:EventSelectButton DataContext="{Binding SomeDataContext}" Width="{Binding SomeDataContextWidth}"/>
</Window>

EDIT

To make the solution complete requires the properties in the UserControl to be changed to use DependencyProperty objects instead. Below are the updates to the codes:

XAML:

<UserControl x:Class="Kiosk.EventSelectButton"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Kiosk"
    x:Name="root"             
    Height="130" Width="130">
    <Grid x:Name="LayoutRoot" DataContext="{Binding ElementName=root}">
        <Button 
            Style="{DynamicResource DarkButton130x130}" 
            HorizontalAlignment="Left" 
            VerticalAlignment="Center">
            <Grid Margin="0,0,0,0" Height="118" Width="118" >
                <Image VerticalAlignment="Top" HorizontalAlignment="Center" Source="image/select_button_arrows.png" />
                <Image Source="{Binding EventImage}" VerticalAlignment="Center" HorizontalAlignment="Center" Effect="{DynamicResource KioskStandardDropShadow}"  />
                <TextBlock Text="{Binding SelectText}" VerticalAlignment="Top" HorizontalAlignment="Center" Margin="0,-2,0,0" FontSize="10pt" Foreground="#5aaff5" />
                <TextBlock Text="{Binding Label}" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="0,0,0,0" FontSize="14pt" FontWeight="Bold" />
            </Grid>
        </Button>
    </Grid>
</UserControl>

Code-Behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Kiosk
{
    public partial class EventSelectButton : UserControl
    {
        public static readonly DependencyProperty EventImageProperty =
                DependencyProperty.Register(
                "EventImage",
                typeof(string),
                typeof(EventSelectButton));

        public String EventImage
        {
            get { return (string)GetValue(EventImageProperty); }
            set { SetValue(EventImageProperty, value); }
        }

        public static readonly DependencyProperty SelectTextProperty =
                DependencyProperty.Register(
                "SelectText",
                typeof(string),
                typeof(EventSelectButton));

        public String SelectText
        {
            get { return (string)GetValue(SelectTextProperty); }
            set { SetValue(SelectTextProperty, value); }
        }

        public static readonly DependencyProperty LabelProperty =
                DependencyProperty.Register(
                "Label",
                typeof(string),
                typeof(EventSelectButton));

        public String Label
        {
            get { return (string)GetValue(LabelProperty); }
            set { SetValue(LabelProperty, value); }
        }

        public EventSelectButton()
        {
            InitializeComponent();
        }
    }
}
karmicpuppet
That's interesting, I tried doing ElementName binding expressions before I posted the question, and they didn't work. e.g. <Image Source="{Binding ElementName=uc, Path=EventImage}" - when setting x:Name="uc" in the parent UserControl, failed. (same for setting the DataContext on both the containing grid and the child items individually.)
slomojo
But I take it that setting the DataContext of a parent is supposed to be inherited by it's children?
slomojo
Yes. If you set the DataContext on the Grid, every element within it will inherit that DataContext. One thing you would want to do also in your code above is to either a) change the properties of your UserControl to be DependencyProperty objects (recommended) or b) Implement INotifyPropertyChanged on your UserControl, raising the PropertyChanged event on the property setters.
karmicpuppet
By the way, what exactly didn't work when you tried ElementName? Did you get any errors? Or did the Bindings just didn't seem to work? If it's the latter, try incorporating my suggested changes on the properties of the UserControl. My initial suspicion is that it has something to do with PropertyChanged notifications.
karmicpuppet
I'll give that a shot... there were no errors, just the applied changes to the properties didn't appear.
slomojo
Done. Good to know it worked.
karmicpuppet
haha. thanks. just glad to help. =)
karmicpuppet
Just to clarify, if I didn't have the dependency properties would the original method break if I updated the values?
slomojo
Well, if you didn't use dependency properties, yeah, it will "break" somewhat. Your bindings will not get notified whenever the UserControl properties get set to new values. So for instance, if you have a button that sets myUserControl.SelectText="New Text" somewhere, the actual property will get updated but the <TextBlock> that's bound to this will not get updated. Using dependency properties fixes this. An alternative, as I've suggested above, is to implement INotifyPropertyChanged.
karmicpuppet
Excellent, many thanks.
slomojo