WPF MVVM firing code based on Tab SelectedValue, r

2019-08-02 14:12发布

问题:

In WPF with MVVM it's easy to fire some code when the user changes the tab.

<TabControl Margin="0 5 5 5" Background="#66F9F9F9" SelectedIndex="{Binding TabIndex}">

And then in the ViewModel:

private int _tabIndex;
public int TabIndex
{
    get { return _tabIndex; }
    set
    {
        if(_tabIndex != value)
        {
            _tabIndex = value;
            OnPropertyChanged("TabIndex");

            if(value == 1)
            {
                //do something
            }
        }
    }
}

But I'm vaguely uncomfortable with this. What if another developer happens along later and adds another tab in the "1" position. If this is application-critical code (which it is), things will break spectacularly.

Danger can be minimized with unit tests, of course. But it made me wonder: is this seen as bad practice? And is there a way of doing this that allows you to refer to the Tab with a string, rather than an int? I tried noodling with binding to the SelectedValue property, but nothing seemed to happen when the tabs were changed.

回答1:

As with all collection controls, the best way to maintain the selected item is to use the SelectedItem property. If you data bind a property of the relevant data type to the TabControl.SelectedItem property, then you'll still be able to tell which tab is selected and select a different one from the view model.

The only problem with this method is that you'll also need to use the TabControl.ItemsSource property to set up the TabItems:

<TabControl ItemsSource="{Binding YourDataItems}" SelectedItem="{Binding YourItem}" />

If you want to try this, then you should know that defining the TabItems can be a little bit confusing. Please refer to the answer from the How to bind items of a TabControl to an observable collection in wpf? question for help with that.



回答2:

You could make a behavior for TabItem, listening for changes to the IsSelected dependency property, and raises a Command when the tab is selected. This can be extended to any number of tabs, each which invokes different commands in the viewmodel. You could also supply a command parameter for any optional context:

class TabSelectedBehavior : Behavior<TabItem>
{
    public static readonly DependencyProperty SelectedCommandProperty = DependencyProperty.Register("SelectedCommand", typeof(ICommand), typeof(TabSelectedBehavior));

    public ICommand SelectedCommand
    {
        get { return (ICommand)GetValue(SelectedCommandProperty); }
        set { SetValue(SelectedCommandProperty, value); }
    }

    private EventHandler _selectedHandler;

    protected override void OnAttached()
    {
        DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
        if (dpd != null)
        {
            _selectedHandler = new EventHandler(AssociatedObject_SelectedChanged);
            dpd.AddValueChanged(AssociatedObject, _selectedHandler);
        }

        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
        if (dpd != null && _selectedHandler != null)
        {
            dpd.RemoveValueChanged(AssociatedObject, _selectedHandler);
        }

        base.OnDetaching();
    }

    void AssociatedObject_SelectedChanged(object sender, EventArgs e)
    {
        if (AssociatedObject.IsSelected)
        {
            if (SelectedCommand != null)
            {
                SelectedCommand.Execute(null);
            }
        }
    }
}

XAML

<TabControl>
    <TabItem Header="TabItem1">
            <i:Interaction.Behaviors>
                <local:TabSelectedBehavior SelectedCommand="{Binding TabSelectedCommand}"/>
            </i:Interaction.Behaviors>
        </TabItem>
        <TabItem Header="TabItem2">
    </TabItem>
</TabControl>

In a similar fashion you could also make a behavior for the TabControl, which turns the SelectionChanged event into a command, and pass the Tag object of the selected TabItem as command parameter.



标签: c# wpf mvvm