microsoft / XamlBehaviorsWpf

Home for WPF XAML Behaviors on GitHub.
MIT License
852 stars 140 forks source link

How to deal with the routed event? #149

Closed MyZQL closed 2 months ago

MyZQL commented 9 months ago

Bug Description I just want to setup an EventTrigger for a TabControl to catch its SelectionChanged event. However, when I add some ComboBoxes to the TabItems, I find that the EventTrigger will be also triggered and invoke the command action, despite the SourceObject has been only set to the TabControl. Here is my Xaml code:

<Grid>
    <TabControl x:Name="MyTabControl">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged" SourceObject="{Binding ElementName=MyTabControl}">
                <i:InvokeCommandAction Command="{Binding MyCommand}"
                                       CommandParameter="">
                </i:InvokeCommandAction>
            </i:EventTrigger>
        </i:Interaction.Triggers>

        <TabItem Header="Tab1">
            <StackPanel Margin="20" Height="30" Orientation="Horizontal" >
                <TextBlock  Text="Combo1" VerticalAlignment="Center"></TextBlock>
                <ComboBox Width="160" ItemsSource="{Binding  MyComboItems}"></ComboBox>
            </StackPanel>
        </TabItem>

        <TabItem Header="Tab2">

            <StackPanel Margin="20" Height="30" Orientation="Horizontal" >
                <TextBlock  Text="Combo2" VerticalAlignment="Center"></TextBlock>
                <ComboBox Width="160" ItemsSource="{Binding  MyComboItems}"></ComboBox>
            </StackPanel>

        </TabItem>
    </TabControl>

</Grid>

And the ViewModel code:

public class MainViewModel : ObservableObject
    {
        public ICommand MyCommand { get; }

        public ObservableCollection<string> MyComboItems { get; } = new()
        {
            "1", "2", "3", "4", "5",
        };

        public MainViewModel()
        {
            MyCommand = new RelayCommand<string>(MyCommandMethod);
        }

        private void MyCommandMethod(string msg)
        {
            MessageBox.Show("OnSelectionChanged");
        }

    }

Expected behavior I only want to catch the SelectionChanged event of the TabControl, not including that of its child controls.

brianlagunas commented 2 months ago

Please provide a sample app that reproduces your scenario or this issue will be closed.

MyZQL commented 2 months ago

The example project is attached. I originally want to bind the "MyCommand" only on the "SelectionChanged" event of the TabControl, but when I change the selected item of anyone of the ComboBox "Combo1" and "Combo2", the "MyCommand" also will be invoked. That's not I want. Is it a bug? wpf-behavior-test.zip 企业微信截图_1725427663917

brianlagunas commented 2 months ago

This is not a bug. ComboBox and TabControl are derived from Selector, and the SelectionChanged event is a routed event, so the ComboBox.SelectionChanged will be routed to parent control TabControl. This is the WPF routed event behavior.

You have a few options:

  1. Don't use an attached behavior and instead just handle the event in the code behind so you can check the OriginalSource
    private void MyTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
    // Check if the event is raised by the TabControl itself
    if (e.OriginalSource is TabControl)
    {
        // Execute your command here
        var command = (DataContext as YourViewModel)?.MyCommand;
        if (command != null && command.CanExecute(null))
        {
            command.Execute(null);
        }
    }
    }
  2. Create a custom behavior to check the OriginalSource