使用MVVm

时间:2017-10-09 14:44:53

标签: c# wpf xaml mvvm

我确实有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的情况下更改选定的Tab项?

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

  

“System.Windows.Data错误:40:BindingExpression路径错误:'索引'   'object'''MainWindowViewModel'找​​不到属性   (的HashCode = 22018304)”。 BindingExpression:路径= AAA;   DataItem ='MainWindowViewModel'(HashCode = 22018304);目标元素是   'TabControl'(Name =''); target属性为'IsSelected'(类型   '布尔')“

更新

控制:TransitionPresenter来自Fluid DLL

更新

我想要隐藏标签项的标题,这样就没有人可以点击标题了,只有通过用户控件中的btns才能通过标题导航导航

2 个答案:

答案 0 :(得分:2)

您可以在视图中为每个视图模型类型定义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

答案 1 :(得分: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";
        }
    }
}

您会对其他视图模型执行相同的操作,只更改每个视图模型的“title”属性。

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

MainViewModel将包含一组BaseViewModel和一个“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设置为你的MainViewModel)

<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”中的按钮将绑定到此命令,该命令是使用RelativeSource绑定的“MainViewModel”的属性:

<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>