WPF ComboBox SelectionChanged event firing twice - wpf

In my DataGrid I am using DataGridComboBoxColumn as follows. Its SelectionChanged event (defined below) always fires twice - once when I click on an item, and then again when I select the new item from the dropdown. When I click on the item that I want to change the SelectionChanged event fires and shows the old value, and then when I select on a new value it fires again and correctly show the new value. But I want the event to be fired only when I select a new value for the combobox.
Question: What is causing this behavior and how can the issue be fixed?
Remark: Many users online seem to have similar issues posted here but none of them helped resolve my issue - maybe, the context is a bit different here. Moreover, the XAML and the code seem ok as it correctly displays the combobox values along with the correctly combox selected values for each row in the grid. Plus, the SelectionChanged event does correctly show the newly selected value but when it fires the second time. Similar code is shown here.
<DataGridComboBoxColumn Header="StartTime" SelectedItemBinding="{Binding localTime}" ItemsSource="{StaticResource localTimeList}">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<EventSetter Event="SelectionChanged" Handler="MyComboBoxColumn_SelectionChanged"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
The event:
private void MyComboBoxColumn_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox comboBox = (ComboBox)sender;
var selectedVal = comboBox.SelectedValue.ToString();
}

What is causing this behavior?
The SelectionChanged event is raised initially when you enter the edit mode and the SelectedItem property is being bound to your source property.
How can the issue be fixed?
The easiest way to handle this is to check whether the ComboBox has been loaded and simply return from the event handler immediately if it hasn't:
private void MyComboBoxColumn_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox comboBox = (ComboBox)sender;
if (!comboBox.IsLoaded)
return;
//handle an actual selection here...
}

Related

WPF SelectedItem binding to source doesn't affect keyboard navigation

OK, so I'm using a typical Binding to my ViewModel. It works beautifully, to source or to target, or so it seems. The vm collection is an ObservableCollection which is initialized and never modified (no setter).
public ObservableCollection<Statement> StatementsList { get; } = new();
#region SelectedStatement
private Statement _selectedStatement;
public Statement SelectedStatement
{
get => _selectedStatement;
set => Set(ref _selectedStatement, value, nameof(SelectedStatement));
}
#endregion SelectedStatement
I can set SelectedStatement from the ViewModel, and the UI updates fine. I can watch the SelectionChanged event of the DataGrid and confirm the added items and removed items are exactly as expected.
Then, I select a different row USING THE MOUSE, and use my search function to select another row using SelectedItem = some statement, which visually selects the row perfectly (again), confirmed by the SelectionChanged event again. SelectedStatement in my view model has the correct value!
Then, the weirdness starts. I press the down arrow the keyboard.
You'd expect the next line after the selected statement to be selected, but instead the next line after the previously selected item (using the mouse) is selected. It's like the keyboard responding code in the DataGrid is not recognizing the prior new row selection via the VM.
Has anyone seen this behavior? I've done WPF development for many years, and I've seen many weird WPF bugs, but this one I've never noticed!
Note that IsSynchronizedWithCurrentItem="True" on the DataGrid. I tried setting it to false just as a stab in the dark, but no change in behavior. I also tried changing my SelectedItem property to wrap a call to GetDefaultCollectionView() and getting/changing the selected item via the collection view instead of using a binding to SelectedItem. The behavior is identical.
Selecting an item is essentially setting IsSelected = true.
And setting this property does not affect the Focus transition to the selected element in any way.
And when controlling from the keyboard, the transition occurs from the element with Focus.
You can add the SelectionChanged processing to the Selector (ListBox, DataGrid,...) and in it perform the Focus transition to the selected item (by the index in the SelectedIndex).
An example of such a handler:
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is Selector selector)
{
int index = selector.SelectedIndex;
if (index >=0)
{
var element = selector.ItemContainerGenerator.ContainerFromIndex(index);
if (element is UIElement uiElement)
uiElement.Focus();
}
}
}

Checkbox in silverlight DataGrid behaving strangely

I am using checkbox in an itemtemplate column in a Silverlight 5 DataGrid.
I am facing a strange problem with it. When I select more than one checkbox and then scroll the grid up and down, the selection shifts to some other checkbox.
I fixed this problem in my code. I did handling within the LoadingRow and UnloadingRow events of the grid.
As soon as a row is loaded, we need to look for the condition on the basis of which we want to keep the check-box checked or unchecked. But as soon as you set the IsChecked property, Checked or UnChecked event of the check-box will get fired.
In this scenario we can unregister the Checked and UnChecked events of the check-box if we have any, set the IsChecked property. After setting this, again register the events.
Below is the code for your help.
Add LoadingRow and UnloadingRow events to your grid.
... LoadingRow="DGUserList_RowLoadUnload" UnloadingRow="DGUserList_RowLoadUnload">
In your code behind file:
private void DGUserList_RowLoadUnload(object sender, DataGridRowEventArgs e)
{
DataGridRow row = e.Row;
CheckBox cbox = (CheckBox)this.dgUserList.Columns[0].GetCellContent(row);
this.UpdateHookedEventsForCheckBox(cbox, false);
cbox.IsChecked = true; // Here put your condition for check/uncheck
this.UpdateHookedEventsForCheckBox(cbox, true);
}
private void UpdateHookedEventsForCheckBox(CheckBox chkBox, bool register)
{
if (register)
{
chkBox.Checked += this.CheckBox_Checked;
chkBox.Unchecked += this.CheckBox_Unchecked;
}
else
{
chkBox.Checked -= this.CheckBox_Checked;
chkBox.Unchecked -= this.CheckBox_Unchecked;
}
}
This way I need not bother about putting some hake code in my Checked and UnChecked events.
This is a known behaviour since Silverlight is re-using its graphical resources in the DataGrid. There's a discussion about it in this Silverlight thread.
It seems one way to fix it is to databind the IsSelected property:
My solution at that time was to add a new property in my data source:
IsSelected, and to bind the checkbox to that value.
You have more additional info in this thread, where Microsoft answers:
This is not a bug. What happens when you scroll around in the
DataGrid is the same checkboxes are being used for new data because
the DataGrid recycles the visuals. When your new data has different
values, the check will change through the Binding and you'll receive
the event. What you can do to get this scenario to work is to listen
to LoadingRow which is raised when a row comes into view. In there,
you can call column.GetCellContents to get the contents of the cell.
This will give you the CheckBox, and you can attach to CheckChanged at
this time. If you do this, you need to do something similar and
listen to UnloadingRow so you can detach the eventhandler when the
checkbox is scrolled out of view.

DataGrid SelectedIndex Changed Event Handler

I have a WPF DataGrid which I'm trying to auto-scroll using this code:
private void mydatagrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
mydatagrid.ScrollIntoView(mydatagrid.CurrentItem);
}
The problem I'm having is that this event handler is not called every time the SelectedIndex of the DataGrid changes. Yes, it does get called when I click on the grid or add or delete an item, but it does NOT get called upon some other actions, such as moving the currently selected item up or down in the underlying collection, which is set via:
mydatagrid.ItemsSource = Seq;
(where Seq is an object of a type derived from ObservableCollection<>).
In my troubleshooting attempts I bound the text of a TextBlock to the SelectedIndex property of mydatagrid. The index number updates perfectly, regardless of what action I perform on the datagrid and/or underlying data source.
So my question is: how can I create an event handler in C# code that gets called upon ANY and ALL changes to the value of SelectedIndex, just like my little textblock binding example does?
I've tried finding a propertyChanged event for the DataGrid, but to no avail. I've tried tapping into all of the events of the DataGrid that seemed remotely related, but to no avail. I've also tried using the CollectionChanged event of the underlying collection, but this doesn't seem to be nicely synchronized with the datagrid (at least at the moment that the event occurs).
Thanks

WPF Default Button command not triggering binding in textbox

I have a search screen with some textboxes and a Search button as the default. If I type in a textbox and I CLICK the button, everything's great. But if I press enter within a text box, the button command fires but the binding on whatever text box I was in does NOT fire and so my criteria doesn't make it to the view model to get filtered on.
I know one fix is to set the bindings on the text boxes to PropertyChanged, but this seems like way overkill. I might have logic in the viewmodel doing stuff and I don't want that to trigger on every single keystroke.
What I really want is a way for the button itself to either trigger a focus change or somehow trigger binding. Or to have the textbox trigger binding if focus is lost OR I press enter OR a command is executed from anywhere
One way to do this is with a BindingGroup.
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.bindinggroup.aspx
If your TextBox(es) and Button are both contained within a Grid (for example), you would add a BindingGroup like this:
<Grid>
<Grid.BindingGroup>
<BindingGroup Name="bindingGroup1"/>
</Grid.BindingGroup>
Then you could add a Click event handler to your button and call CommitEdit() on the BindingGroup (which the Button and TextBox inherit from the Grid):
private void button1_Click(object sender, RoutedEventArgs e)
{
(sender as FrameworkElement).BindingGroup.CommitEdit();
}
The Button.Click event fires before the CommandBinding, so any databound TextBox or any other databound controls within that BindingGroup should be updated before your view model command gets executed.
I've had the exact scenario you just mentioned. The trick I use is an attached behavior that sits on a control and listens for the PreviewKeyDown event. It checks if enter is being pressed. If so it forces the control to lose focus, thus causing the binding to fire before the command executes.
A simpler approach (rather than using a binding group) is to use the default button's click event to set the focus to itself. As this happens before the command is executed it means the ViewModel is updated in time.
private void button1_Click(object sender, RoutedEventArgs e)
{
(sender as Button).Focus()
}
And if you really hate code behind, you could always write an attached property...

How to capture a mouse click on an Item in a ListBox in WPF?

I want to get notified when an item in a ListBox gets clicked by the mouse, whether it is already selected or not.
I searched and found this: (http://kevin-berridge.blogspot.com/2008/06/wpf-listboxitem-double-click.html see the comments)
private void AddDoubleClickEventStyle(ListBox listBox, MouseButtonEventHandler mouseButtonEventHandler)
{
if (listBox.ItemContainerStyle == null)
listBox.ItemContainerStyle = new Style(typeof(ListBoxItem));
listBox.ItemContainerStyle.Setters.Add(new EventSetter()
{
Event = MouseDoubleClickEvent,
Handler = mouseButtonEventHandler
});
}
//Usage:
AddDoubleClickEventStyle(listView1, new MouseButtonEventHandler(listView1_MouseDoubleClick));
This works, but it does it for a DoubleClick. I can't get it working for a single click though. I tried MouseLeftButtonDownEvent - as there doesn't seem to be a MouseClick event, but it's not being called.
A bit more general side question: How can I see what events do exist and which handlers correspond to them and when they actually do something? For example, what tells me that for a MouseDoubleClickEvent I need a MouseButtonEventHandler? Maybe for a MouseLeftButtonDownEvent I need some other handler and that's why it's not working?
I also tried subclassing ListBoxItem and override OnMouseLeftButtonDown - but it doesn't get called either.
Marc
I believe that your MouseLeftButtonDown handler is not called because the ListBox uses this event internally to fire its SelectionChanged event (with the thought being that in the vast majority of cases, SelectionChanged is all you need). That said, you have a couple of options.
First, you could subscribe to the PreviewLeftButtonDown event instead. Most routed events have a routing strategy of Bubbling, which means that the control that generated the event gets it first, and if not handled, the event works its way up the visual tree giving each control a chance at handling the event. The Preview events, on the other hand, are Tunneling. This means that they start at the root of the visual tree (generally Window), and work their way down to the control that generated the event. Since your code would get the chance to handle the event prior to the ListBoxItem, this will get fired (and not be handled) so your event handler will be called. You can implement this option by replacing MouseDoubleClickEvent in your sample with PreviewMouseLeftButtonDown.
The other option is to register a class handler that will be notified whenever a ListBoxItem fires the MouseLeftButtonDown event. That is done like this:
EventManager.RegisterClassHandler(typeof(ListBoxItem),
ListBoxItem.MouseLeftButtonDownEvent,
new RoutedEventHandler(this.MouseLeftButtonDownClassHandler));
private void OnMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
}
Class Handlers are called before any other event handlers, but they're called for all controls of the specified type in your entire application. So if you have two ListBoxes, then whenever any ListBoxItem is clicked in either of them, this event handler will be called.
As for your second question, the best way to know what type of event handler you need for a given event, and to see the list of events available to a given control, is to use the MSDN documentation. For example, the list of all events handled by ListBoxItem is at http://msdn.microsoft.com/en-us/library/system.windows.controls.listboxitem_events.aspx. If you click on the link for an event, it includes the type of the event handler for that event.
There is also another way - to handle PreviewMouseDown event and check if it was triggered by the list item:
In XAML:
<ListBox PreviewMouseDown="PlaceholdersListBox_OnPreviewMouseDown"/>
In codebehind:
private void PlaceholdersListBox_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var item = ItemsControl.ContainerFromElement(sender as ListBox, e.OriginalSource as DependencyObject) as ListBoxItem;
if (item != null)
{
// ListBox item clicked - do some cool things here
}
}
Was inspired by this answer, but it uses listbox by name, I propose to use sender argument to avoid unnecessary dependencies.
I think the first option in Andy's answer, of using PreviewMouseLeftButtonDown, is the way to go about this. In XAML it would look like this:
<ListBox Name="testListBox">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter
Event="PreviewMouseLeftButtonDown"
Handler="ListBox_MouseLeftButtonDown" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
There is another way to get MouseDown event in ListBox. You can add event handler for events that are marked as handled by using handledEventsToo signature of AddHandler method:
myListBox.AddHandler(UIElement.MouseDownEvent,
new MouseButtonEventHandler(ListBox_MouseDown), true);
Third parameter above is handledEventsToo which ensures that this handler will be invoked no matter if it is already marked as Handled (which ListBoxItem does in ListBox).
See Marking Routed Events as Handled, and Class Handling for explanation.
See How to Attach to MouseDown Event on ListBox for example.
You can use Event="MouseLeftButtonUp"
Unlike "PreviewLeftButtonDown" it will get the ListBoxItem handled too.
You can use the SelectionChangedEventArgs argument of the SelectionChanged event to find what item is add or removed through AddedItems and RemovedItems, usually only have the latest clicked on, or if not, then look at the last item which is the count-1.

Resources