42

Is it possible to determine whether a Selector.SelectionChanged event was initiated by the user or programmatically?

I.e. I need something like a boolean "IsUserInitiated" property that is true only if the SelectionChanged event was raised because the user changed the selection using mouse or keyboard.

  • If it was initiated programmatically it was likely your own code that did it. Can't you plug in whatever you need at that point? – Jon Sep 14 '11 at 06:49
  • Can you use the `KeyBoard` and `Mouse` action events (both normal and `Preview`) and if they result in selection (a selection which wasnt there in the `Preview` event) then conclude that it is a user made selection? – WPF-it Sep 14 '11 at 08:17
  • 4
    I am starting a bounty for this question, because I also frequently need to determine whether a selection was initiated by a user or programmatically. The problem is that WPF sometimes places the SelectionChanged event (or alternatively the SelectedIndex property change notification) on the dispatcher queue; i.e. it is signaled asynchronously. Then, there is no way to "plug in" a marker that my own code triggered the change. –  Jun 21 '12 at 15:40
  • To illustrate the problem, I have pasted some sample code here: http://pastebin.com/6M2tWgEZ . –  Jun 21 '12 at 18:22
  • Well if you correctly follow binding pattern, and if you do not overwrite same value again, I dont think it matters whether selection was changed by user or anyone else. The whole reason WPF was developed around concepts of binding, is to separate binding from the events. Coming from WinForm, many things are missing, SelectionChanging is also missing. But probably it is not needed in new model of programming. Why do you need to know whether it was initiated by user or not? – Akash Kava Jun 26 '12 at 11:56
  • Possible duplicate of [could the SelectionChanged event in WPF be handled only for user interaction?](http://stackoverflow.com/questions/14301271/could-the-selectionchanged-event-in-wpf-be-handled-only-for-user-interaction) – Arash Oct 14 '16 at 14:36

8 Answers8

25

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;
}
jim31415
  • 8,588
  • 6
  • 43
  • 64
17

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
    }
}
David Sherret
  • 101,669
  • 28
  • 188
  • 178
  • 10
    I tried this, however, it does not work for me if the user uses the keyboard and hits the Enter key to make a selection. At that time, the value of IsDropDownOpen is false. – Fahad Mar 28 '14 at 16:53
  • Worked for me. I know it's not perfect, but should work on many situations. Thank you, wasted 1 hour trying to figure this out... – Nanaki Jul 25 '16 at 14:22
  • Just a note of something I'm going to try, maybe using IsKeyboardFocusedWithin might be more reliable. It covers tab stops and when the drop down is open. – chris84948 Oct 03 '16 at 20:11
  • Maybe use if(cboStatus.IsDropDownOpen || cboStatus.IsKeyboardFocused) to cover changing value with keyboard. – vvucetic May 15 '17 at 12:32
11

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
        }
    }
War10ck
  • 12,387
  • 7
  • 41
  • 54
Justin Pavatte
  • 1,198
  • 2
  • 12
  • 18
  • Might be prudent to use try/finally to ensure that `_loading` is reset to false even if something fails. – fadden Jul 13 '23 at 22:54
7

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...

Community
  • 1
  • 1
mlemay
  • 1,622
  • 2
  • 32
  • 53
  • 3
    "Generally, you can always know whether it is initiated programmatically because it's your code that initiates it." How does that work when the selection is changed asynchronously by WPF? –  Jun 21 '12 at 18:28
  • 2
    "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." - if the ultimate purpose of the OP is to react to a user-initiated change, and not a binding-initiated change (not set a boolean flag on the event itself), this is the way to go. – Julia Hayward Apr 15 '15 at 09:34
3

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.

harri
  • 556
  • 5
  • 17
  • 1
    If I understood your implementation correctly, you assume that the `SelectionChanged` event is fired synchronously between the `*Down` and `*Up` events. Did you find any confirmation in the MSDN documentation that this always is the case and that `SelectionChanged` never is fired asynchronously when the user changes the selection? –  Jun 22 '12 at 07:16
  • 1
    nope ... but in my experience the system will not do that asynchronously since all the events are fired in the Dispatcher thread anyway. The problem with the "other" solution (try to set a flag or something when you change it in code) is that in this case the Binding may cause an asynchronous operation. I might be wrong though. – harri Jun 22 '12 at 10:25
  • 1
    I want my upvote to be removed, if you press mousedown and hold it, and meanwhile some code alters the selection, you'd have a problem. – EricG Nov 24 '14 at 15:49
1

Usually a Selector has it's selection set/changed when the control is loaded into view. When this happens the IsLoaded property is still false. When a user makes a selection manually the control obviously has to be visible and hence IsLoaded will be true. Try using this property to determine if a change is user initiated or due to the control being loaded.

Andreas Zita
  • 7,232
  • 6
  • 54
  • 115
  • This turned out to help me filter the events I cared about. If you have code that changes selection after the control is loaded, you may want to look at other properties. – RedMatt Mar 03 '21 at 20:23
0

Why do you want to know? I have coded many dialogs where I had similar situations - I didn't really want to know that the user used the mouse or keyboard, but I did want a specific behaviour, and I did want effects from triggering some binding to behave the right way.

For most cases I have found that using the MVVM pattern - or at least separating logic from ui - you often avoid those problems.

So for your problem I would try to eliminate the selectionchanged handler and only use bindings - so your state of the gui is based on the model behind and not the wireing of events.

mvvm: http://en.wikipedia.org/wiki/Model_View_ViewModel

Rune Andersen
  • 1,650
  • 12
  • 15
  • 1
    I don't know for what user911275 needs it, but in my case it is to implement dialogs that show a "Do you want to save your changes?" message box when the user presses Cancel and has modified any control since the dialog was opened. Programmatic changes, however, shall not trigger such a message box. –  Jun 28 '12 at 09:24
  • A good example. I would have the wpf ui reflect the underlaying object - an unchanged default one - and have it display the contents of that so no object state comes from the controls. Then you can either have a deep copy of it to compare against or set a flag in the properties when they change - you could forexample do that with a proxy object based on DynamicObject. – Rune Andersen Jun 28 '12 at 10:44
  • It is a lot easier to manage than bindings to elements and events that fire and make data changes. – Rune Andersen Jun 28 '12 at 10:52
  • There are cases when you cannot compare a copy of the unmodified underlaying object against its current state to check whether it is unmodified. For example, if object fields contain rich text (e.g. HTML, RTF, ...). There are many representations for the same text, and HTML and RTF controls tend to convert the text into an internal format, sometimes asynchronously. –  Jun 28 '12 at 12:18
  • If the control you are using isn't coded to give information when it is changed you have a hard time no matter which way you do it - if it changes it in another thread without notification, you have little way of telling when it is changed. I still think MVVM makes many things easier :) – Rune Andersen Jun 28 '12 at 13:06
  • There are many examples of when you want to differentiate between user input and code-based changes. Analytics tracking and undo / redo are two off the top of my head. – Josh Noe Mar 01 '18 at 22:49
  • @JoshNoe when this question was originally asked 7 years ago there wasn't many that wanted to track stuff in WPF - most just wanted it to work - that was why I wrote my answer that way. Go ahead and downvote old answers - there are probably loads that are less relevant now :) – Rune Andersen Mar 11 '18 at 21:39
0

You can check for AddedItems and RemovedItems. If it was initiated by user both properties has an item. If an item was just added via code the RemovedItems list should be empty. So

if (e.AddedItems.Count>0 && e.RemovedItems.Count > 0) //Initiated by user

Andreas
  • 3,843
  • 3
  • 40
  • 53