2

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.

Yacoub Massad
  • 27,509
  • 2
  • 36
  • 62
  • Have you tried it in an empty project with just the above code ? If so, I'll test this shortly if no one else has responded with an answer. – netniV Sep 18 '15 at 18:38
  • Yes. This code is actually from an empty project I created to troubleshoot my issue. – Yacoub Massad Sep 18 '15 at 18:41

3 Answers3

1

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

Community
  • 1
  • 1
netniV
  • 2,328
  • 1
  • 15
  • 24
  • Thank you for your answer and effort. I tried the workaround and it works. However, I am not sure if this can work if you have multiple DateTimePicker objects on the same window. Is there a way? Do you see any issue with just using the 2.4 version? – Yacoub Massad Sep 21 '15 at 09:14
  • It can yes but you would need different selected value backings and this is something I prefer these days because it gives you a clearer picture of what's going on and these will show up through different properties and senders to the **DateTimeValueProperty_Changed** even if you reuse the same static method. There are always newer features and bug fixes (and inherently bugs) with newer versions so you just have to pick a version that works for you. – netniV Sep 21 '15 at 09:18
  • When the picker loses focus (when I select another control, e.g. the listbox) the changed event is fired, I think I have to manually check for the value in the event handler. I tested your workaround with multiple pickers and it works. Until the bug is fixed, I will either use V2.4 or use your workaround. – Yacoub Massad Sep 22 '15 at 19:28
1

I solved this problem/bug by checking the originalsource of the event.

 if(e.OriginalSource is Xceed.Wpf.Toolkit.DateTimePicker)
    {
       if(((Xceed.Wpf.Toolkit.DateTimePicker)e.OriginalSource).IsFocused == true)
       {
          ResetDataTable();
       }
    }

Since I don't display the timepicker in this control, I also make sure that it's the datetimepicker that has focus, and not the timepicker. Might be redundant

Hallgeir Engen
  • 841
  • 9
  • 10
0

I solved this by comparing the original source and the source.

e.OriginalSource == e.Source
Gapster
  • 41
  • 5