Search code examples
c#wpfwpftoolkit

DateTimePicker OnValueChanged event is raised twice


I am using the DateTimePicker WPF control from the Extended WPF Toolkit Community Edition library version 2.5.

My problem is that when I pick a date, the OnValueChanged event is raised twice instead of just once.

Here is the code I am using:

XAML:

<StackPanel>
    <xctk:DateTimePicker AutoCloseCalendar="True"  Name="picker"  Width="400" Height="40" ValueChanged="UpDownBase_OnValueChanged"/>
    <ListBox Height="300" Name="listbox"></ListBox>
</StackPanel>

C# code behind:

private void UpDownBase_OnValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    var value = picker.Value;

    if (value == null)
        listbox.Items.Add("[NULL]");
    else
        listbox.Items.Add(value.Value.ToString(CultureInfo.InvariantCulture));
}

Now, whenever I pick a new date, the list box will be populated with two new items. I also debugged the program and confirmed that the event handler is actually invoked twice.

How can I solve this issue?

UPDATE:

I tried version 2.4 and it seems that the issue is gone. It seems to me now that this might be a possible bug in version 2.5.


Solution

  • This would appear to be because the event in 2.5 is being fired from:

    at Xceed.Wpf.Toolkit.DateTimePicker.OnValueChanged(Nullable`1 oldValue, Nullable`1 newValue) in C:\Users\Mark Vinten\Downloads\wpftoolkit-114314\Main\Source\ExtendedWPFToolkitSolution\Src\Xceed.Wpf.Toolkit\DateTimePicker\Implementation\DateTimePicker.cs:line 264
    

    And then subsequently from the base class:

    at Xceed.Wpf.Toolkit.TimePicker.OnValueChanged(Nullable`1 oldValue, Nullable`1 newValue) in C:\Users\Mark Vinten\Downloads\wpftoolkit-114314\Main\Source\ExtendedWPFToolkitSolution\Src\Xceed.Wpf.Toolkit\TimePicker\Implementation\TimePicker.cs:line 264
    

    Now the base class, also seems to go through the CLR binding process suggesting that this is the bound value. I'm still looking into why that would be, but a workaround is to use Binding as such:

    MainWindow.cs

        public DateTime? DateTimeValue
        {
            get { return (DateTime?)GetValue(DateTimeValueProperty); }
            set { SetValue(DateTimeValueProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for DateTimeValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DateTimeValueProperty =
            DependencyProperty.Register("DateTimeValue", typeof(DateTime?), typeof(MainWindow), new PropertyMetadata(null, new PropertyChangedCallback(DateTimeValueProperty_Changed)));
    
        private static void DateTimeValueProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MainWindow mw = d as MainWindow;
            System.Diagnostics.Debug.WriteLine("d is " + d == null ? "null" : d.GetType().FullName);
            if (mw != null && e.Property == DateTimeValueProperty)
            {
                var value = e.NewValue as DateTime?;
                var listbox = FindChild<ListBox>(mw, "listbox");
    
                if (value == null)
                    listbox.Items.Add("[NULL]");
                else
                    listbox.Items.Add(value.Value.ToString(System.Globalization.CultureInfo.InvariantCulture));
            }
        }
    
        /// <summary>
        /// Finds a Child of a given item in the visual tree. 
        /// </summary>
        /// <param name="parent">A direct parent of the queried item.</param>
        /// <typeparam name="T">The type of the queried item.</typeparam>
        /// <param name="childName">x:Name or Name of child. </param>
        /// <returns>The first parent item that matches the submitted type parameter. 
        /// If not matching item can be found, 
        /// a null parent is being returned.</returns>
        public static T FindChild<T>(DependencyObject parent, string childName)
           where T : DependencyObject
        {
            // Confirm parent and childName are valid. 
            if (parent == null) return null;
    
            T foundChild = null;
    
            int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < childrenCount; i++)
            {
                var child = VisualTreeHelper.GetChild(parent, i);
                // If the child is not of the request child type child
                T childType = child as T;
                if (childType == null)
                {
                    // recursively drill down the tree
                    foundChild = FindChild<T>(child, childName);
    
                    // If the child is found, break so we do not overwrite the found child. 
                    if (foundChild != null) break;
                }
                else if (!string.IsNullOrEmpty(childName))
                {
                    var frameworkElement = child as FrameworkElement;
                    // If the child's name is set for search
                    if (frameworkElement != null && frameworkElement.Name == childName)
                    {
                        // if the child's name is of the request name
                        foundChild = (T)child;
                        break;
                    }
                }
                else
                {
                    // child element found.
                    foundChild = (T)child;
                    break;
                }
            }
    
            return foundChild;
        }
    

    MainWindow.xaml

        <StackPanel DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}">
            <xctk:DateTimePicker AutoCloseCalendar="True"  Name="picker"  Width="400" Height="40" Value="{Binding DateTimeValue}" />
            <ListBox Height="300" Name="listbox"></ListBox>
        </StackPanel>
    

    This uses the binding system which automatically checks whether the value has changed and only raises events if it has.

    Note: FindChild<> was a function that I found on this How can I find WPF controls by name or type? post

    Update with final summary

    The reason for this appears to be is because there is a TimePicker embedded within the DateTimePicker to provide the functionality. Unfortunately, both DateTimePicker and TimePicker derive from the same base and thus raise the same routed event within UpDownBase where T is DateTime?.

    if you check on the event arguments, e.RoutedEVent is always UpDownBase.OnValueChanged since this is the class raising the event. e.Source or e.OriginalSource is always the DateTimePicker itself meaning you have no useful way to filter out one or the other event.

    There is code within DateTimeUpDown.RaiseValueChangedEvent() to check if the TemplatedParent is a TimePicker to prevent re-raising but whether the event is raised from the DateTimePicker or the TimePicker the TemplatedParent always seems to be the DateTimePicker so that fails thus you get the event twice.

    I have raised a bug with the findings on the WPFToolkit project site: https://wpftoolkit.codeplex.com/workitem/22014