【问题标题】:Navigate through TabItem using MVVm使用 MVVm 浏览 TabItem
【发布时间】:2017-10-11 01:08:25
【问题描述】:

假设我有 3 个用户控件(TIShowNames、TIEnterCode、TIShowFactor)。 他们有自己的观点和相应的 viewModel。

所有这 3 个都在 mainwindowView 中。

这是我的 mainwindowView Xaml:

<Controls:TransitionPresenter Name="transContainer" Grid.Row="2" RestDuration="0:0:1" IsLooped="False" Transition="{StaticResource SlideTransition}">
        <TabControl Name="TCMain" Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">



            <TabItem Name="TIShowNames" Visibility="Collapsed">
                <views:NameView x:Name="NameViewElement" />
            </TabItem>
            <TabItem Name="TIEnterCode" Visibility="Collapsed">
                 <views:CodeView x:Name="CodeViewElement"  />
            </TabItem>
            <TabItem Name="TIShowFactor" Visibility="Collapsed">
                <views:FactorDetailView x:Name="FactorDetailViewElement"  />
            </TabItem> 

        </TabControl>
    </Controls:TransitionPresenter>

在我以前的编程风格中,我曾经使用这行代码来导航选项卡项(没有任何模式):

 private void ChangeTabItemTo(TabItem TI)
    {

        transContainer.ApplyTransition("TCMain", "TCMain");
        TCMain.SelectedItem = TI;
    }

我在“TIShowNames”中有一个 btn 节目,所以当我点击它时,它必须转到“TIShowFactor”。 在 MVVM 中,ViewModel 对视图一无所知(此项目选项卡在其父视图中!!!)。那么他如何在不违反 MVVM 的情况下更改选定的选项卡项??

另一个尝试: 由于此错误,更改 Selectedindex 将不起作用:

“System.Windows.Data 错误:40:BindingExpression 路径错误:'索引' 在“对象”“MainWindowViewModel”上找不到属性 (哈希码=22018304)'。绑定表达式:路径=AAA; DataItem='MainWindowViewModel' (HashCode=22018304);目标元素是 'TabControl'(名称='');目标属性是“IsSelected”(类型 '布尔')"

更新

Controls:TransitionPresenter 来自 Fluid DLL

更新

我想隐藏标签项的标题,所以没有人可以单击标题,并且只能通过用户控件中的 btns 导航到标题

【问题讨论】:

    标签: c# wpf xaml mvvm


    【解决方案1】:

    您可以在视图中为每个视图模型类型定义一个DataTemplate

    <TabControl Name="TCMain"
                ItemsSource="{Binding ViewModels}"
                SelectedItem="{Binding ViewModel}"
                Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
        <TabControl.ContentTemplate>
            <DataTemplate>
                <ContentControl Content="{Binding}">
                    <ContentControl.Resources>
                        <DataTemplate DataType="{x:Type local:NameViewViewModel}">
                            <views:NameView />
                        </DataTemplate>
                        <DataTemplate DataType="{x:Type local:CodeViewViewModel}">
                            <views:CodeView />
                        </DataTemplate>
                        <DataTemplate DataType="{x:Type local:FactorDetailViewModel}">
                            <views:FactorDetailView />
                        </DataTemplate>
                    </ContentControl.Resources>
                </ContentControl>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
    

    ...并将SelectedItem 属性绑定到您在视图模型中设置的源属性,例如:

    public object ViewModel
    {
        get { return _vm; }
        set { _vm = value; NotifyPropertyChanged(); }
    }
    ...
    ViewModel = new CodeViewViewModel(); //displays the CodeView
    

    【讨论】:

    • 您可能还应该提到,这需要将选项卡视图模型添加到主视图模型的列表属性中,并且TabControl.ItemsSource 绑定到该属性。以防万一从您的示例代码中不明显。
    • 我虽然是:)
    • TabControl.ItemsSource 应设置为 ObservableCollection。什么可以代替T?视图模型不一样。
    • "ViewModel = new CodeViewViewModel();" 只会在您处于主窗口视图模型时工作。例如“NameViewViewModel” 无法访问它
    • 您可以使用 ObservableCollection 或 ObservableCollection,其中 T 是所有视图模型类型实现的基类或接口。
    【解决方案2】:

    扩展 mm8 的答案,这就是我的做法:

    首先,我将创建一个 BaseViewModel 类,供每个视图模型继承,它代表 TabControl 的每个选项卡。

    我喜欢将它实现为一个带有名为“Title”的抽象字符串属性的抽象类,因此我可以动态创建选项卡并显示它们的名称(或标题)。该类还将实现 NotifyPropertyChanged 接口。

    public abstract class BaseViewModel : INotifyPropertyChanged
    {
        public abstract string Title { get; }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    然后我将创建从这个基本视图模型继承的每个视图模型。例如:

    public class NameViewModel : BaseViewModel
    {
        public override string Title
        {
            get
            {
                return "Name";
            }
        }
    }
    

    您可以对其他视图模型执行相同的操作,只需更改每个视图模型的“标题”属性。

    现在我将创建应用程序的 MainView 及其对应的视图模型。

    MainViewModel 将有一个 BaseViewModels 的集合和一个“CurrentViewModel”(BaseViewModel 类型),并将您想要的所有视图模型添加到其构造函数的集合中,如下所示:

    public class MainViewModel : BaseViewModel
    {
        public override string Title
        {
            get
            {
                return "Main";
            }
        }
    
        private ObservableCollection<BaseViewModel> _viewModels;
        public ObservableCollection<BaseViewModel> ViewModels
        {
            get { return _viewModels; }
            set
            {
                if (value != _viewModels)
                {
                    _viewModels = value;
                    OnPropertyChanged();
                }
            }
        } 
    
        private BaseViewModel _currentViewModel;
        public BaseViewModel CurrentViewModel
        {
            get { return _currentViewModel; }
            set
            {
                if (value != _currentViewModel)
                {
                    _currentViewModel = value;
                    OnPropertyChanged();
                }                
            }
        }
    
    
        public MainViewModel()
        {
            ViewModels = new ObservableCollection<BaseViewModel>();
            ViewModels.Add(new NameViewModel());
            ViewModels.Add(new CodeViewModel());
            ViewModels.Add(new FactorDetailViewModel());
        }
    }
    

    最后,您的主视图将类似于 mm8 发布的内容:

    (请注意我的代码与 mm8 的代码的区别:(1)您需要将 TabControl 的 DisplayMemberPath 设置为 BaseViewModels 的“Title”属性,并且(2)您需要将 Window 的 DataContext 设置为您的主视图模型)

    <Window ...>
        <Window.DataContext>
            <local:MainViewModel/>
        </Window.DataContext>
        <Grid>
            <TabControl Name="TCMain"
                ItemsSource="{Binding ViewModels}"
                DisplayMemberPath="Title"
                SelectedItem="{Binding CurrentViewModel}"
                Background="#00FFFFFF" BorderThickness="0" Padding="0 -5 0 0 ">
                <TabControl.ContentTemplate>
                    <DataTemplate>
                        <ContentControl Content="{Binding}">
                            <ContentControl.Resources>
                                <DataTemplate DataType="{x:Type local:NameViewModel}">
                                    <local:NameView />
                                </DataTemplate>
                                <DataTemplate DataType="{x:Type local:CodeViewModel}">
                                    <local:CodeView />
                                </DataTemplate>
                                <DataTemplate DataType="{x:Type local:FactorDetailViewModel}">
                                    <local:FactorDetailView />
                                </DataTemplate>
                            </ContentControl.Resources>
                        </ContentControl>
                    </DataTemplate>
                </TabControl.ContentTemplate>
            </TabControl>
        </Grid>
    </Window>
    

    现在它应该可以按预期工作了。每次更改 TabControl 的活动选项卡时,控件的 SelectedItem 属性都会更改为相应的视图模型,该视图模型将被模板化为其对应的视图。

    顺便说一下,这种方法称为“View Model First”(而不是 View First)。

    编辑

    如果您想在具有更改当前视图模型命令的视图模型之一上有一个按钮,您可以这样做:

    我想您应该熟悉 Josh Smith 的 RelayCommand。如果不是,只需在网络上搜索它的实现即可。

    您需要在 MainViewModel 上创建一个 ICommand 属性,该属性将负责更改“CurrentViewModel”属性:

    private ICommand _showFactorDetailCommand;
    public ICommand ShowFactorDetailCommand
    {
        get
        {
            if (_showFactorDetailCommand == null)
            {
                _showFactorDetailCommand = new RelayCommand(p => true, p => show());
            }
            return _showFactorDetailCommand;
        }
    }
    
    private void show()
    {
        CurrentViewModel = ViewModels.Single(s => s.Title == "Factor");
    }
    

    上面的 show() 方法只是搜索标题为“Factor”的视图模型集合并将其设置为 CurrentViewModel,而 CurrentViewModel 又将是 ContentControl 的内容,它充当您内部 TabControl 的 ContentTemplate主视图。

    请记住,您的 FactorDetailViewModel 应按如下方式实现:

    public class FactorDetailViewModel : ViewModelBase
    {
        public override string Title
        {
            get
            {
                return "Factor";
            }
        }
    }
    

    “NameView”内的按钮将绑定到此命令,该命令是“MainViewModel”使用RelativeSource 绑定的属性:

    <Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowFactorDetailCommand}" Content="Show Factor" Height="20" Width="60"/>
    

    您可以使该命令更通用,将您想要导航到的视图模型的标题作为命令参数传递:

    private ICommand _showCommand;
    public ICommand ShowCommand
    {
        get
        {
            if (_showCommand == null)
            {
                _showCommand = new RelayCommand(p => true, p => show(p));
            }
            return _showCommand;
        }
    }
    
    private void show(p)
    {
        var vm = (string)p;
        CurrentViewModel = ViewModels.Single(s => s.Title == vm);
    }
    

    然后在你的视图上,也传递命令参数:

    <Button Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowCommand}" Content="Show Factor" CommandParameter="Factor" Height="20" Width="60"/>
    

    最后,要完全隐藏您的 TabItems,您需要设置 TabControl 的 ItemContainerStyle,以便 TabItems 的 Visibility 具有“Collapsed”的值。

    <TabControl.ItemContainerStyle>
        <Style TargetType="{x:Type TabItem}">
            <Setter Property="Visibility" Value="Collapsed"/>
        </Style>
    </TabControl.ItemContainerStyle>
    

    【讨论】:

    • 当您单击选项卡项标题时,这可能没问题。但这是我的情况:我在“TIShowNames”中有一个 btn“show”,所以当我点击它时,它必须转到“TIShowFactor”。这个 btn 在我的用户控件中,并且知道关于 tabcntrol 或选项卡项的任何事情
    • 好的。我将编辑我的答案,解释如何实现这一目标。
    猜你喜欢
    • 1970-01-01
    • 2014-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-05
    • 1970-01-01
    相关资源
    最近更新 更多