Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine whether Selector.SelectionChanged event was initiated by a user

Tags:

wpf

Simple work around:

You could create a method that temporarily disables the SelectionChanged event and call it when you need to change the selection programmatically.

private void SelectGridRow( int SelectedIndex )
{
    myDataGrid.SelectionChanged -= myDataGrid_SelectionChanged;
    myDataGrid.SelectedIndex = SelectedIndex;

    // other work ...

    myDataGrid.SelectionChanged += myDataGrid_SelectionChanged;
}

This should work in most scenarios:

private void cboStatus_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (this.cboStatus.IsDropDownOpen)
    {
        //OPTIONAL:
        //Causes the combobox selection changed to not be fired again if anything
        //in the function below changes the selection (as in my weird case)
        this.cboStatus.IsDropDownOpen = false;

        //now put the code you want to fire when a user selects an option here
    }
}

This is a problem I have had to work around since WinForms. I was hoping that in WPF they would add a boolean to SelectionChangedEventArgs called something like IsUserInitiated as mentioned in the question. I have most commonly needed this when I want to ignore anything happening while the data is loading and binding to the screen. For example, say I am defaulting a field based on the new value in SelectionChanged BUT I want the user to be able to overwrite this default value, and I only want the user to overwrite it, NOT the application when the screen reloads. I still feel like what I have been doing is hacky, but I will post it because I don't see it mentioned. No fancy tricks, just simple and effective.

1) Create a class level boolean called _loading

private bool _loading;

2) Update the boolean in the method doing the loading

private async Task Load()
{
    _loading = true;
    //load some stuff
    _loading = false;
}

3) Use the boolean whenever you need to

    private void SetDefaultValue(object sender, SelectionChangedEventArgs e)
    {
        if (!_loading) {
            //set a default value
        }
    }

Taken from http://social.msdn.microsoft.com where the user post the same question

I don't think we can distinguish whether a SelectionChanged event was initiated by the user input or programmatically. SelectionChanged event doesn't care that.

Generally, you can always now whether it is initiated programmatically because it's your code that initiates it.

If you use DataBinding to bind the SelectedItem, you can set the NotifyOnSourceUpdated and NotifyOnTargetUpdated properties to True. And you can handle the Binding.SourceUpdated and Binding.TargetUpdated events. In most cases, the change initiated by the user inputs goes from Target to Source. If the change is initiated programmatically, it goes from Source to Target.

I don't know if it can help...


You could use an custom routed event and hook up the appropriate handlers in an behavior like this:

    public class UserSelectionChangedEventArgs : RoutedEventArgs
    {
        public UserSelectionChangedEventArgs( RoutedEvent id, SelectionChangedEventArgs args , bool changedByUser) :base(id)
        {
            SelectionChangedByUser = changedByUser;
            RemovedItems = args.RemovedItems;
            AddedItems = args.AddedItems;
        }

        public bool SelectionChangedByUser { get; set; }
        public IList RemovedItems { get; set; }
        public IList AddedItems { get; set; }
    }
    public delegate void UserSelectionChangedEventHandler( object sender, UserSelectionChangedEventArgs e );

    public class UserSelectionChangedBehavior : Behavior<Selector>
    {
        private bool m_expectingSelectionChanged;

        public static readonly RoutedEvent UserSelectionChangedEvent = EventManager.RegisterRoutedEvent( "UserSelectionChanged", RoutingStrategy.Bubble, typeof( UserSelectionChangedEventHandler ), typeof( Selector ) );

        public static void AddUserSelectionChangedHandler( DependencyObject d, UserSelectionChangedEventHandler handler )
        {
            ( (Selector) d ).AddHandler( UserSelectionChangedEvent, handler );
        }

        public static void RemoveUserSelectionChangedHandler( DependencyObject d, UserSelectionChangedEventHandler handler )
        {
            ( (Selector) d ).RemoveHandler( UserSelectionChangedEvent, handler );
        }

        private void RaiseUserSelectionChangedEvent( UserSelectionChangedEventArgs args )
        {
            AssociatedObject.RaiseEvent( args );
        }

        protected override void OnAttached()
        {
            AssociatedObject.PreviewKeyDown += OnKeyDown;
            AssociatedObject.PreviewKeyUp += OnKeyUp;
            AssociatedObject.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
            AssociatedObject.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
            AssociatedObject.SelectionChanged += OnSelectionChanged;
            base.OnAttached();
        }

        protected override void OnDetaching()
        {
            AssociatedObject.PreviewKeyDown -= OnKeyDown;
            AssociatedObject.PreviewKeyUp -= OnKeyUp;
            AssociatedObject.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDown;
            AssociatedObject.PreviewMouseLeftButtonUp -= OnMouseLeftButtonUp;
            AssociatedObject.SelectionChanged -= OnSelectionChanged;
            base.OnDetaching();
        }

        private void OnMouseLeftButtonUp( object sender, MouseButtonEventArgs e )
        {
            m_expectingSelectionChanged = false;
        }

        private void OnKeyDown( object sender, KeyEventArgs e )
        {
            m_expectingSelectionChanged = true;
        }

        private void OnKeyUp( object sender, KeyEventArgs e )
        {
            m_expectingSelectionChanged = false;
        }

        private void OnMouseLeftButtonDown( object sender, MouseButtonEventArgs e )
        {
            m_expectingSelectionChanged = true;
        }

        private void OnSelectionChanged( object sender, SelectionChangedEventArgs e )
        {
            RaiseUserSelectionChangedEvent( new UserSelectionChangedEventArgs( UserSelectionChangedEvent, e, m_expectingSelectionChanged ) );
        }
    }

In XAML you could just subscribe to the UserSelectionChangedEvent like this:

<ListBox ItemsSource="{Binding Items}"  b:UserSelectionChangedBehavior.UserSelectionChanged="OnUserSelectionChanged">
  <i:Interaction.Behaviors>
    <b:UserSelectionChangedBehavior/>
  </i:Interaction.Behaviors>

Handler:

private void OnUserSelectionChanged( object sender, UserSelectionChangedEventArgs e )
{
    if(e.SelectionChangedByUser)
    {
        Console.WriteLine( "Selection changed by user" );
    }
    else
    {
        Console.WriteLine( "Selection changed by code" );
    }
}

This is just an idea. Probably you won't even need the behavior and just define the attached routed event. But then I have no idea where to store the m_expectingSelectionChanged flag. I also don't know if this works in all cases. But maybe it gives you a starting point.