David Hope
David Hope

Reputation: 2256

TwoWay binding on WPF UserControl

I'm trying to build a simple Color Picker as a WPF UserControl, but I'm having trouble getting the selected color back to the main window.

My main window XAML looks like this:

<Window x:Class="ColorPicker.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:view="clr-namespace:ColorPicker"
        Title="TestWindow" SizeToContent="WidthAndHeight">
    <StackPanel Orientation="Horizontal">
        <Rectangle Fill="{Binding MyColor}" Margin="10,10,10,10" Width="100" Height="300"/>
        <view:WPFColorPicker SelectedColor="{Binding MyColor, Mode=TwoWay}" Width="200" Height="300"/>
    </StackPanel>
</Window>

and the view model and code-behind:

namespace ColorPicker
{
    public class TestViewModel
    {
        public TestViewModel()
        {
            MyColor = new SolidColorBrush(Color.FromRgb(255, 0, 0));
        }

        public Brush MyColor { get; set; }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new TestViewModel();
        }
    }
}

The WPFColorPicker usercontrol XAML is:

<UserControl x:Class="ColorPicker.WPFColorPicker"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <Rectangle x:Name="rtlfill" Fill="{Binding SelectedColor}" HorizontalAlignment="Stretch" Height="60" Margin="10,10,10,10" Stroke="Black" VerticalAlignment="Top"/>
        <ListBox HorizontalAlignment="Stretch" SelectedValue="{Binding SelectedColor}" VerticalAlignment="Stretch" Margin="0,0,0,81" ScrollViewer.HorizontalScrollBarVisibility="Disabled" x:Name="colorList">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel IsItemsHost="True" Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Rectangle Fill="{Binding .}" Margin="0,0,0,5" Width="20" Height="20" Stroke="#FF211E1E" OpacityMask="Black" StrokeThickness="1" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</UserControl>

and code-behind:

namespace ColorPicker
{
    public partial class WPFColorPicker : UserControl
    {
        List<Brush> brushList;

        public WPFColorPicker()
        {
            InitializeComponent();

            brushList = new List<Brush>() {
                new SolidColorBrush(Color.FromRgb(255,  0,  0)),
                new SolidColorBrush(Color.FromRgb(  0,255,  0)),
                new SolidColorBrush(Color.FromRgb(  0,  0,255))};

            this.colorList.ItemsSource = brushList;
            DataContext = this;
        }

        public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register("SelectedColor",
            typeof(Brush),
            typeof(WPFColorPicker),
            new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Red), 
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

        public Brush SelectedColor
        {
            get { return (Brush)GetValue(SelectedColorProperty); }
            set { SetValue(SelectedColorProperty, value); }
        }
    }
}

So, the problem I'm having is that binding on SelectedColor (using MyColor from TestViewModel) doesn't work.

From looking at other questions on StackOverflow and various tutorials, I think have the UserControl set up correct to bind SelectedColor as a TwoWay DependencyProperty, but its not working.

Can someone provide me some insight?

Upvotes: 1

Views: 1155

Answers (2)

dkozl
dkozl

Reputation: 33364

Each FrameworkElement can have only one DataContext so when you do

DataContext = this;

in UserControl constructor you overwrite DataContext normally inherited through visual tree and that affects default binding context for WPFColorPicker and all children, including

<view:WPFColorPicker SelectedColor="{Binding MyColor, Mode=TwoWay}" .../>

Remove that line from WPFColorPicker constructor and instead give UserControl some name

<UserControl x:Class="ColorPicker.WPFColorPicker" ... x:Name="myUserControl">

and change bindings inside UserControl to use that name

<Rectangle ... Fill="{Binding ElementName=myUserControl, Path=SelectedColor}"/>
<ListBox ... SelectedValue="{Binding ElementName=myUserControl, Path=SelectedColor}">

EDIT

As a side note you need to be aware that Brush is compared by reference so ListBox.SelectedValue will not preselect value unless it's one of instances from the brushList, which is not possible as you create the list every time. Basically two different instances of SolidColorBrush, even with the same Color, are different for equality check

Upvotes: 3

Mike Eason
Mike Eason

Reputation: 9713

Assuming that your Window's DataContext is set to your ViewModel.

Your colour picker is a UserControl, therefore the following binding:

SelectedColor="{Binding MyColor, Mode=TwoWay}"

Is trying to find MyColor in the DataContext of the UserControl, not the parent window.

You can either give the window a name and use ElementName binding:

SelectedColor="{Binding DataContext.MyColor, ElementName=windowName, Mode=TwoWay}"

Or even better, use a relative source binding:

SelectedColor="{Binding DataContext.MyColor, RelativeSource={RelativeSource AncestorType={x:Type Window}}, Mode=TwoWay}"

Upvotes: 0

Related Questions