locked
Explicit UpdateSourceTrigger using Dependency Properties of type Collection in an UserControl RRS feed

  • Question

  • Hello.

    I am trying to design a UserControl containing a DependencyProperty of type BindingList<string> (I also tried with List<string> or ObservableCollection<string> with the same results). However, I am unable to understand why, when an item is added to the DependencyProperty, it is immediately propagated to the source even if the UpdateSourceTrigger is set to Explicit. Is there any way to make this action Explicit?

    Here is my code for the UserControl:

    <UserControl x:Class="ItemSource_binding.MyListControl"
                 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" >
            <StackPanel>
                <ListBox MinHeight="80" MinWidth="100" Name="MyListBox" ItemsSource="{Binding Path=Items}"/>
            <Button Click="ButtonAddClick" Content="Add item" Margin="0,5,0,5" />
        </StackPanel>
    </UserControl>

    Here is the class for this control:

    using System;
    using System.Collections;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    
    namespace ItemSource_binding
    {
        public partial class MyListControl : UserControl
        {
            public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
              "Items",
              typeof(BindingList<string>),
              typeof(MyListControl),
              new FrameworkPropertyMetadata
              {
                  BindsTwoWayByDefault = true,
                  DefaultUpdateSourceTrigger = UpdateSourceTrigger.Explicit,
              }
            );
    
            public BindingList<string> Items
            {
                get { return (BindingList<string>)GetValue(ItemsProperty); }
                set { SetValue(ItemsProperty, value); }
            }
    
            public MyListControl()
            {
                InitializeComponent();
                (this.Content as FrameworkElement).DataContext = this;
            }
    
            private void ButtonAddClick(object sender, RoutedEventArgs e)
            {
                Items.Add("New item");
            }
        }

    And here the XAML for the main window:

    <Window x:Class="ItemSource_binding.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:controls="clr-namespace:ItemSource_binding"
            Title="MainWindow" Width="Auto" Height="Auto" >
        <StackPanel Orientation="Vertical">
            <Label Content="List with UpdateSourceTrigger = Explicit" />
            <controls:MyListControl Items="{Binding Path=items, UpdateSourceTrigger=Explicit}"/>
            <Label Content="List with UpdateSourceTrigger = PropertyChanged" />
            <controls:MyListControl Items="{Binding Path=items, UpdateSourceTrigger=PropertyChanged}"/>
            <Button Click="UpdateSource" Content="UpdateSource" Margin="0,10,0,10" Background="Bisque" />
        </StackPanel>
    </Window>


    Last, the code of the Window itself:

    using System.Windows;
    using System.Windows.Data;
    using System.ComponentModel;
    
    namespace ItemSource_binding
    {
        public partial class MainWindow : Window
        {
            public BindingList<string> items { get; set; }
    
            public MainWindow()
            {
                items = new BindingList<string> { "string1", "string2", "string3", "string4" };
                this.DataContext = this;
                InitializeComponent();
            }
    
            private void AddItems(object sender, RoutedEventArgs e)
            {
                items.Add("Added item");
            }
    
            private void UpdateSource(object sender, RoutedEventArgs e)
            {
                foreach (var child in BindingOperations.GetSourceUpdatingBindings(this))
                    child.UpdateSource();
            }
        }
    }

    I would expect that a change to the ItemsProperty in the first UserControl (MyListControl) does not propagate unless I click Update Source button, but both the lists are kept in sync and I don't know why. Do you find any mistake here?




    • Edited by EB335 Friday, August 21, 2015 10:10 AM
    Friday, August 21, 2015 10:09 AM

Answers

  • Personally, I would use entity framework for my data and validate the entity/entities.

    You need the changes in the entity to do that.

    I would just discard the changes if they hit no.

    You can set the entity state to unchanged.

    http://www.c-sharpcorner.com/UploadFile/ff2f08/discard-changes-without-disposing-dbcontextobjectcontext-in/

    entry.State = EntityState.Unchanged; 

    .

    As it is, you'd have to work with the data in the window itself.

    That's a bad idea and never going to be mvvm. If this is business software then you should be using mvvm.


    • Marked as answer by EB335 Friday, August 21, 2015 1:22 PM
    Friday, August 21, 2015 1:17 PM

All replies

  • You have a complication there because you're using a dependency property. They raise change notification.

    http://www.codeproject.com/Articles/34741/Change-Notification-for-Dependency-Properties

    With BindingList and Observablecollection (also) raise collectionchanged as items are added or removed.

    I think bindinglist can also raise notification on property changed, but I don't use them.

    List does none of those.

    Everyone uses observablecollection.

    As well as collection changed there is also property changed.

    Property changed is about changes to a property, that can be the collection if a property is a collection but it's usually used for notification of a property in a type which is in that collection.

    https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396

    Note the code in the setter of properties which would be a property in the T of Observablecollection<T> you have bound for a colllection

    So if you imagine you have an ObservableCollection<Customer> and Customer has a CustomerName property:

            public string CustomerName
            {
                get
                {
                    return this.customerNameValue;
                }
    
                set
                {
                     this.customerNameValue = value;
                     NotifyPropertyChanged();
                }
            }

    When you set the customername that fires propertychanged on that property.

    It's in a collection.

    If you add a new customer to that observablecollection, that raises collection changed.

    If your property is a dependency property then the metadata you have controls when it (also ) notifies.

    .

    Note also that the source is the target is the thing in the view you see the binding on, the source is the thing it is bound to.

    This

    UpdateSourceTrigger=Explicit}"/

    Controls when changes in the view are propagated to the list it's bound to rather than when changes in the target propagate to the view.

    If you wanted to control that then use things that do not notify.

    A plain property ( Not a dependency property ) which is a List<t> where t implements inotifypropertychanged.

    Leave the notifypropertychanged out the setter:

    public string CustomerName {get;set;}

    Then explicitly raise propertychanged yourself whenever you want to do so.

    NotifyPropertyChanged("CustomerName");

    (Read the link).



    • Edited by Andy ONeill Friday, August 21, 2015 10:39 AM
    • Proposed as answer by Sabyasachi.M Friday, August 21, 2015 11:08 AM
    Friday, August 21, 2015 10:37 AM
  • Thank you Andy for your answer.

    Maybe I am not understanding properly the notification chain:

    You wrote that

    UpdateSourceTrigger=Explicit

    controls when changes in the view are propagated to the list it's bound to.

    From my perspective this is what I need: I just want to delay the changes to items property (in MainWindow) when (only when) the UpdateSource button is pressed. This approach works perfectly for any other control (such as a TextBox where Text DependecyProperty is bound to a string property). I would expect that, if UpdateSourceTrigger is set to Explicit, the changes notified by NotifyPropertyChanged are not reflected to the source.

    (The two lists in my example were kept for a visual feedback of what is going on, but I am watching the source of the binding in Visual Studio debugger)

    Where am I wrong?




    • Edited by EB335 Friday, August 21, 2015 11:09 AM
    Friday, August 21, 2015 11:05 AM
  • If you just bound one string to one plain string property, you'd be good.

    You're not though,

    You have a collection.

    When you add a new row in the view it'll propagate binding changes and create a new item of the collection type.

    I don't really follow why you'd want to avoid that and my advice would be not to try.


    Friday, August 21, 2015 11:34 AM
  • I understand your concerns about the approach I am trying to follow. This custom control is part of a modal dialog form, where a significant number of variables (boolean, string, number, and other) are changed with the Confirm/Discard behavoir. The dialog is customizable by editing the XAML (loaded on runtime) and linking a number of properties (lists of type string are one of the possible property types), so I was trying to follow the UpdateSourceTrigger=Explicit approach for all these controls. When the user presses the OK button, I traverse the visual tree and I update all the sources (simulated by the Update Source button in my example).

    If I have to create a copy of a hundred of properties for this dialog and write them back to the main data context (or if I need to use a different method just for this custom control) at least I want to understand why binding changes are propagated even if UpdateSourceTrigger is set to explicit.

    Let me know if I am understanding correctly: if I use a DependencyProperty of type Collection, a Property change notification is raised when the collection is changed, and the change to the source is not delayed even if UpdateSourceTrigger is set to Explicit. Is this correct?

    If I use a standard property and I want to bind items in MainWindow with the content of the list in XAML, I still need to use a DependencyProperty. I need to copy the content of the collection bound to this property to my internal plain property. Now, how do I update back the source when UpdateSource is called (or, how do I tell the DependecyProperty that its value is to be recomputed)?

    By the way: unless I'm wrong the binding to the Text property of a TextBox in XAML is between a DependencyProperty of type Text and a plain property of type string. The difference between this binding type and one between a plain property of type List<string> or ObservableCollection<string> and a DependencyProperty of the same type is not completely clear to me...

    Thank you again for your patience

    -


    • Edited by EB335 Friday, August 21, 2015 12:58 PM
    Friday, August 21, 2015 12:50 PM
  • Personally, I would use entity framework for my data and validate the entity/entities.

    You need the changes in the entity to do that.

    I would just discard the changes if they hit no.

    You can set the entity state to unchanged.

    http://www.c-sharpcorner.com/UploadFile/ff2f08/discard-changes-without-disposing-dbcontextobjectcontext-in/

    entry.State = EntityState.Unchanged; 

    .

    As it is, you'd have to work with the data in the window itself.

    That's a bad idea and never going to be mvvm. If this is business software then you should be using mvvm.


    • Marked as answer by EB335 Friday, August 21, 2015 1:22 PM
    Friday, August 21, 2015 1:17 PM
  • Another thought.

    It's fairly easy to loop through all the properties in an object and copy them to a new version in order to clone it.

    If you're not using ef or don't want to work on your "original" instance then you could use reflection to copy all the properties.

    Here's an example piece of code looks OK.

    http://www.codeproject.com/Articles/38270/Deep-copy-of-objects-in-C

    using System;
    using System.Reflection;
    
    namespace HAKGERSoft {
    
        public class HCloner {
    
            public static T DeepCopy<T>(T obj) {
                if(obj==null)
                    throw new ArgumentNullException("Object cannot be null");
                return (T)Process(obj);
            }
    
            static object Process(object obj) {
                if(obj==null)
                    return null;
                Type type=obj.GetType();
                if(type.IsValueType || type==typeof(string)) {
                    return obj;
                }
                else if(type.IsArray) {
                    Type elementType=Type.GetType(
                         type.FullName.Replace("[]",string.Empty));
                    var array=obj as Array;
                    Array copied=Array.CreateInstance(elementType,array.Length);
                    for(int i=0; i<array.Length; i++) {
                        copied.SetValue(Process(array.GetValue(i)),i);
                    }
                    return Convert.ChangeType(copied,obj.GetType());
                }
                else if(type.IsClass) {
                    object toret=Activator.CreateInstance(obj.GetType());
                    FieldInfo[] fields=type.GetFields(BindingFlags.Public| 
                                BindingFlags.NonPublic|BindingFlags.Instance);
                    foreach(FieldInfo field in fields) {
                        object fieldValue=field.GetValue(obj);
                        if(fieldValue==null)
                            continue;
                        field.SetValue(toret,Process(fieldValue));
                    }
                    return toret;
                }
                else
                    throw new ArgumentException("Unknown type");
            }
    
        }
    }


    Friday, August 21, 2015 1:27 PM
  • Thank you Andy. I'll take a deeper look at the Entity framework. My application already has a database of variables (properties in terms of WPF) but I was trying to avoid explicit copy of the properties (about 1/3 of them are handled by a single dialog box). I'm quite new to WPF and MVVM, so I'm not used enough to it when it comes to put things together (I started to study OWL, then MFC, then I went to QT and I moved to JavaFX...).

    If you have time, I kindly ask you to clarify anyway the difference between the notifications generated by a binding between a string DependencyProperty and a string property, and between a Collection and a Collection DependencyProperty (I always try to fully understand my mistakes before switching to another solution).

    Thank you again!

    Friday, August 21, 2015 4:12 PM