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); }
}
}
}