0

I have a UserControl for quantities called QtyControl, with a DependencyProperty called Qty (an int). I'm registering this property with DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, but if I don't put UpdateSourceTrigger=PropertyChanged on the control consumer's binding, it doesn't work and I don't understand why.

The code should allow you to click the Add button and see whatever number you selected in the ComboBox, but always shows 0.

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"
        WindowStartupLocation="CenterScreen">
  <StackPanel>
    <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}" /> <!--, UpdateSourceTrigger=PropertyChanged - this is needed, but I don't know why when I registered Qty with DefaultUpdateSourceTrigger=PropertyChanged -->
            </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.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;
        }
    }

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

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

        public MyViewModel()
        {
            MyItems = new ObservableCollection<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"
             >
  <Grid>
    <ComboBox Name="_comboBox"
              SelectedIndex="{Binding Qty, RelativeSource={RelativeSource AncestorType=UserControl}}">
      <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;
using System.Windows.Data;

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(
                    defaultValue: 1,
                    flags: FrameworkPropertyMetadataOptions.AffectsArrange,
                    propertyChangedCallback: null,
                    coerceValueCallback: null,
                    isAnimationProhibited: false,
                    defaultUpdateSourceTrigger: UpdateSourceTrigger.PropertyChanged
                    )
                /* Also does not work
                new FrameworkPropertyMetadata(
                    1,
                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
                    )
                */
                );
        }

        public int Qty
        {
            get { return (int)GetValue(QtyProperty); }
            set { SetValue(QtyProperty, value);  }
        }
    }
}
Matt Gregory
  • 8,074
  • 8
  • 33
  • 40
  • As a note, setting `Mode=TwoWay` on the SelectedIndex Binding in the UserControl's XAML is redundant, because the SelectedIndex property of the Combobox already binds TwoWay by default. You can do the same for your Qty property by setting `FrameworkPropertyMetadataOptions.BindsTwoWayByDefault`. – Clemens Dec 31 '18 at 19:26
  • Oh hmm, I wondering why it still worked when I took that off. That's probably a good idea! – Matt Gregory Dec 31 '18 at 19:50
  • Besides that, setting DefaultUpdateSourceTrigger shouldn't be necessary at all. The default behavior is that a bindng source is updated when the target property changes. It is also unclear why you set FrameworkPropertyMetadataOptions.AffectsArrange. – Clemens Dec 31 '18 at 19:53
  • I actually put AffectsArrange in there without knowing if I needed it. I meant to look into it later. The real control is like Amazon's quantity control where there's a combobox with items from 1 to 10+ and if you pick 10+ it turns into a textbox where you can enter a higher number. – Matt Gregory Dec 31 '18 at 20:00
  • All I know is, if I don't put `UpdateSourceTrigger=PropertyChanged` on the usage of the control, it doesn't update. I was hoping setting `DefaultUpdateSourceTrigger` would fix it but it has no effect. – Matt Gregory Dec 31 '18 at 20:02
  • How about `new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)` without all the other redundant settings, and just ``? – Clemens Dec 31 '18 at 20:04
  • There's no difference. – Matt Gregory Dec 31 '18 at 20:06

0 Answers0