Context
Xaml:
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:WpfApp2"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
Title="MainWindow" Height="350" Width="400">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="The ListView:"/>
<ListView
Grid.Row="1"
ItemsSource="{Binding ItemCollection}"
SelectionMode="Single">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction
Command="{Binding Path=ProcessChangeCommand}"
CommandParameter="{Binding Path=SelectedItem,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
</Grid>
</Window>
Code behind:
public partial class MainWindow : Window {
public MainWindow () {
InitializeComponent ();
}
}
public class ViewModel : INotifyPropertyChanged {
bool colorList;
string[] colors = { "blue", "yellow", "green", "orange", "black" };
string[] towns = { "Dakar", "Berlin", "Toronto" };
private ObservableCollection<string> itemCollection;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> ItemCollection {
get => itemCollection;
set {
itemCollection = value;
RaisePropertyChanged (nameof (ItemCollection));
}
}
public ICommand ProcessChangeCommand { get; private set; }
public ViewModel () {
ProcessChangeCommand = new RelayCommand<string> (ProcessChange);
itemCollection = new ObservableCollection<string> (colors);
}
public void ProcessChange (string parameter) {
if (parameter == null) return;
Debug.WriteLine ($"Selected: {parameter}");
ItemCollection = new ObservableCollection<string> (colorList ? colors : towns);
RaisePropertyChanged (nameof (ItemCollection));
colorList = !colorList;
}
void RaisePropertyChanged ([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
}
}
public class RelayCommand<T> : ICommand {
readonly Action<T> _execute = null;
public event EventHandler CanExecuteChanged {
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand (Action<T> excute) { _execute = excute; }
public bool CanExecute (object parameter) => true;
public void Execute (object parameter) => _execute ((T) parameter);
}
Using .Net Framework 4.8.
Add package Microsoft.Xaml.Behaviors.Wpf to the project.
The ListView
displays a list. When a selection is done, its value is shown on the console and the list is swapped (there are two lists shown alternatively).
Problem
The "color" list is longer than the "town" list, and when orange
or black
are selected, after the selection is shown on the console and the list is swapped (normal), the first item of the town list, Dakar
, is triggered (unexpected). When debugging, after clicking orange
, ProcessChange
is invoked 4 times:
- with parameter
orange
(expected), - with parameter
null
(unexpected but understandable and discarded in the code. The call is a reentrant call happenning whileProcessChange
is processingorange
) - with parameter
Dakar
(unexpected and wrong), - with null(same as second bullet, also reentrant, occurring while processing unexpected
Dakar
call)
The resulting console output:
Observation: This double event anomaly disappears if the grid rows are set this way:
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/> <!-- height set to * -->
</Grid.RowDefinitions>
(or if a 100ms delay is introduced, or a breakpoint is set, in the event handler).
And the question is:
What is the reason Dakar
appears on the console after orange
?