1

Can someone explain why the binding on the ListBox (in MainWindow.xaml) DOES work while the same technique done on the local:QtyControl (UserControl) in the DataGrid does NOT work?

The error I'm getting is:

System.Windows.Data Error: 40 : BindingExpression path error: 'QtyRequested' property not found on 'object' ''QtyControl' (Name='')'. BindingExpression:Path=QtyRequested; DataItem='QtyControl' (Name=''); target element is 'QtyControl' (Name=''); target property is 'Qty' (type 'Int32')

What's particularly baffling to me is why it thinks I'm trying to bind to the QtyRequested property on the QtyControl, when I'm trying to bind to that property on MyViewModel.

My goal is to set a value in the ComboBox, click the Add button, and have the MessageBox tell me which value was selected.

MainWindow.xaml:

<Window x:Class="UserControlTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:UserControlTest"
        Title="MainWindow" Height="150" Width="250">
  <StackPanel>
    <ListBox ItemsSource="{Binding MyItems}">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding QtyRequested}" />
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>

    <DataGrid ItemsSource="{Binding MyItems}"
              IsReadOnly="True"
              Height="Auto" Width="Auto"
              HeadersVisibility="Column"
              AutoGenerateColumns="False"
              SelectionMode="Single">
      <DataGrid.Columns>
        <DataGridTemplateColumn Header="Qty" Width="50">
          <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
              <local:QtyControl Qty="{Binding QtyRequested, Mode=TwoWay}" QtyChanged="QtyControl_QtyChanged" />
            </DataTemplate>
          </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Add" Width="50">
          <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
              <Button Click="_AddItemBtn_Click">Add</Button>
            </DataTemplate>
          </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
      </DataGrid.Columns>
    </DataGrid>
  </StackPanel>
</Window>

MainWindow.xaml.cs:

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace UserControlTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MyViewModel();
        }

        private void _AddItemBtn_Click(object sender, RoutedEventArgs e)
        {
            DataGridRow parentRow = _FindDataGridRowFromCl((Control)sender);
            MyItem item = (MyItem)parentRow.Item;
            MessageBox.Show($"QtyRequested = {item.QtyRequested}");
        }

        private DataGridRow _FindDataGridRowFromCl(Control cl)
        {
            for (Visual vi = cl as Visual; vi != null; vi = VisualTreeHelper.GetParent(vi) as Visual)
                if (vi is DataGridRow row)
                    return row;
            return null;
        }

        private void QtyControl_QtyChanged(object sender, RoutedPropertyChangedEventArgs<int> e)
        {

        }
    }

    public class MyItem
    {
        public int QtyRequested { get; set; } = 0;
    }

    public class MyViewModel : INotifyPropertyChanged
    {
        private List<MyItem> _myItems;
        public List<MyItem> MyItems {
            get {
                return _myItems;
            }
            set {
                _myItems = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MyItems)));
            }
        }

        public MyViewModel()
        {
            MyItems = new List<MyItem> { new MyItem() };
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

QtyControl.xaml:

<UserControl x:Class="UserControlTest.QtyControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:UserControlTest"
             mc:Ignorable="d" Height="22" Width="42"
             DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
  <Grid>
    <ComboBox Name="_comboBox"
              SelectedIndex="{Binding Qty}">
      <ComboBox.Items>
        <ComboBoxItem Content="0" IsSelected="True" />
        <ComboBoxItem Content="1" />
        <ComboBoxItem Content="2" />
      </ComboBox.Items>
    </ComboBox>
  </Grid>
</UserControl>

QtyControl.xaml.cs:

using System.Windows;
using System.Windows.Controls;

namespace UserControlTest
{
    public partial class QtyControl : UserControl
    {
        public QtyControl()
        {
            InitializeComponent();
        }

        public static DependencyProperty QtyProperty;

        static QtyControl()
        {
            QtyProperty = DependencyProperty.Register(
                "Qty",
                typeof(int),
                typeof(QtyControl),
                new FrameworkPropertyMetadata(1, new PropertyChangedCallback(_OnQtyChanged))
                );
            QtyChangedEvent = EventManager.RegisterRoutedEvent(
                "QtyChanged",
                RoutingStrategy.Bubble,
                typeof(RoutedPropertyChangedEventHandler<int>),
                typeof(QtyControl)
                );
        }

        public int Qty
        {
            get { return (int)GetValue(QtyProperty); }
            set { SetValue(QtyProperty, value);  }
        }

        private static void _OnQtyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            QtyControl qtyControl = (QtyControl)sender;
            int newQty = (int)e.NewValue;
            qtyControl._comboBox.SelectedItem = newQty;

            int oldQty = (int)e.OldValue;
            RoutedPropertyChangedEventArgs<int> args = new RoutedPropertyChangedEventArgs<int>(oldQty, newQty)
            {
                RoutedEvent = QtyControl.QtyChangedEvent
            };
            qtyControl.RaiseEvent(args);
        }

        public static readonly RoutedEvent QtyChangedEvent;

        public event RoutedPropertyChangedEventHandler<int> QtyChanged
        {
            add { AddHandler(QtyChangedEvent, value); }
            remove { RemoveHandler(QtyChangedEvent, value); }
        }
    }
}
Matt Gregory
  • 8,074
  • 8
  • 33
  • 40
  • 1
    Neither set `this.DataContext = new MyViewModel()` in the UserControl's constructor, nor `DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}` in its XAML. In order to bind to the view model item (with the QtyRequested property) in the inherited DataContext in the DataTemplate, you must not set the UserControl's DataContext at all. – Clemens Dec 30 '18 at 19:08
  • @Clemens Ok, but how do I expose the QtyControl's Qty property to the control consumer so that it can be set with ComboBox? Do I need 2 two-way bindings? – Matt Gregory Dec 30 '18 at 19:16
  • 1
    Sorry for the confusion, but `this.DataContext = new MyViewModel()` should of course be kept in the MainWindow constructor. I was reading too fast. However, you must not set the UserControl's DataContext explicitly. – Clemens Dec 30 '18 at 19:18
  • @Clemens I should have named it MainWindowViewModel probably, so it's not your fault. – Matt Gregory Dec 30 '18 at 19:20
  • @Clemens I've edited the question and removed the DataContext setting from the UserControl, but I still have the same problem and I have no better understanding than I had before :) – Matt Gregory Dec 30 '18 at 19:26
  • 1
    You should now have `SelectedIndex="{Binding Qty, RelativeSource={RelativeSource AncestorType=UserControl}}"` in the UserControl's XAML. – Clemens Dec 30 '18 at 19:27
  • @Clemens: Sorry, that question does answer my question. I was editing the binding on the UserControl tag trying to fix it without realizing what I was doing :0 Thank you! – Matt Gregory Dec 30 '18 at 19:32

0 Answers0