使用 MVVM 将新项目添加到 TabControl 的 ItemSource 时选择最后一个 TabItem

Selecting the last TabItem when new items are added to a TabControl's ItemSource using MVVM

我通过将 ItemsSource 绑定到 MyUnicornsViewModel 创建了一个动态生成的 TabControl

随着新项目添加到 MyUnicornsViewModel... 创建了新的选项卡项目。但是,TabControl 中不会自动选择新添加的选项卡。

如何在添加新标签时选择它们?

<TabControl ItemsSource="{Binding MyUnicornsViewModel}" SelectedItem="{Binding SelectedItem}">
    <TabControl.ItemTemplate>
        <!-- header template -->
        <DataTemplate>
            <TextBlock Text="{Binding Name}" />
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <!-- body template-->
        <DataTemplate>
            <TextBlock Text="{Binding Content}" />
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

起初,我希望 TabControl 中有“ItemsChanged”或“ItemAdded”事件,这样我就可以在添加新项目时在代码隐藏中设置 SelectedIndex。

我尝试的另一件事是将 TabControl.SelectedItem 绑定到 MyUnicornsViewModel 中的 SelectedItem 属性。可悲的是,这也没有用。

MyUnicornsViewModel:

public class MyUnicornsViewModel : ObservableCollection<UnicornViewModel>
{
    ...

    private void AddNewUnicorn()
    {
        var awesomeUnicorn = new UnicornViewModel();
        Add(awesomeUnicorn);
        SelectedItem = awesomeUnicorn;  //I expected my TabControl to have 'awesomeUnicorn' selected.
    }

    public UnicornViewModel SelectedItem { get; set; }
}

这里有几个问题:

  1. ObservableCollection 推导出“视图模型”很奇怪。一个视图模型应该包含一个可观察的集合。
  2. 视图模型需要实现INotifyPropertyChanged接口;从提供的代码中不清楚 UnicornViewModel 是否实现了此接口,但是,MyUnicornsViewModel 绝对没有。

这里有一些建议:

  1. 实现 INotifyPropertyChanged 接口的视图模型基础 class 将真正帮助您完成大部分工作。您可以使用 INotifyPropertyChanged documentation or look for an MVVM framework that fits well with your project (e.g. Prism, MVVM Light, ReactiveUI) 编写自己的代码。其中每一个都将提供一个基础 class 用于视图模型 - BindableBaseViewModelBaseReactiveObject 分别用于上述每个框架。
  2. MyUnicornsViewModel 应该有:
    • 独角兽合集ObservableCollection;这将绑定到 TabControl.
    • 上的 ItemsSource 属性
    • SelectedItem 属性 必须在设置时触发 PropertyChanged 事件。

这是一个使用 Prism 的快速示例:

public sealed class UnicornViewModel : BindableBase
{
    public UnicornViewModel(string name, string content)
    {
        Name = name;
        Content = content;
    }

    // these properties don't change and therefore don't need to raise property changed
    public string Name { get; }

    public string Content { get; }
}

public sealed class UnicornsViewModel : BindableBase
{
    private UnicornViewModel _selectedUnicorn;

    public UnicornsViewModel()
    {
        AddUnicornCommand = new DelegateCommand(AddUnicorn);
        ClearUnicornsCommand = new DelegateCommand(ClearUnicorns, () => HasUnicorns).ObservesProperty(() => HasUnicorns);
    }

    public ObservableCollection<UnicornViewModel> Unicorns { get; } = new ObservableCollection<UnicornViewModel>();

    public UnicornViewModel SelectedUnicorn
    {
        get => _selectedUnicorn;
        set => SetProperty(ref _selectedUnicorn, value, () => RaisePropertyChanged(nameof(HasUnicorns)));
    }

    public DelegateCommand AddUnicornCommand { get; }
    public DelegateCommand ClearUnicornsCommand { get; }
    private bool HasUnicorns => Unicorns.Any(); // helper property for the clear command's can execute

    private void AddUnicorn()
    {
        Unicorns.Add(new UnicornViewModel($"Unicorn {Unicorns.Count + 1}", Guid.NewGuid().ToString()));
        SelectedUnicorn = Unicorns.Last();
    }

    private void ClearUnicorns()
    {
        SelectedUnicorn = null;
        Unicorns.Clear();
    }
}