在使用 Prism 的 MVVM 应用程序中,每次选择 TabItem 时如何 运行 一个方法

How to run a method every time a TabItem is selected, in an MVVM application using Prism

我已经尝试实现它一段时间了,但到目前为止还没有做到,尽管我觉得这应该很容易。

困难来自于我已经使用 MVVM 模式实现了一个 WPF 应用程序。现在,这是我对模式和框架的第一次尝试,所以几乎可以肯定我在尝试遵循 MVVM 准则时犯了错误。

我的实现

我有三个视图及其各自的 ViewModel(使用 Prism 的 AutoWireViewModel 方法连接)。 MainView 有一个 TabControl 和两个 TabItem,每个 包含一个 Frame 容器,Source 设置为另一个两个 View。以下代码摘自 MainView:

<TabControl Grid.Row="1" Grid.Column="1">
    <TabItem Header="Test">
        <!--TestView-->
        <Frame Source="View1.xaml"/>
    </TabItem>
    <TabItem Header="Results">
        <!--ResultsView-->
        <Frame Source="View2.xaml"/>
    </TabItem>
</TabControl>

我的问题

每次有人更改特定 TabItem 时,我都想 运行 一种方法来更新 View 中包含的一个 WPF 控件。该方法已经实现并绑定到 Button,但理想情况下,不需要任何按钮,我希望有某种 Event 来实现这一点。

在此先感谢大家的帮助。

例如,您可以处理 PageLoaded 事件,以便在最初加载视图后调用方法或调用视图模型的命令:

public partial class View2 : Page
{
    public View2()
    {
        InitializeComponent();
        Loaded += View2_Loaded;
    }

    private void View2_Loaded(object sender, RoutedEventArgs e)
    {
        var viewModel = DataContext as ViewModel2;
        if (viewModel != null)
            viewModel.YourCommand.Execute(null);
        Loaded -= View2_Loaded;
    }
}

另一个选项是在 MainViewModel 中处理这个问题。您将 TabControlSelectedItem 属性 绑定到 MainViewModel 的 属性 并将此 属性 设置为 [=17] 的实例=] 或 ViewModel2,具体取决于您要显示的视图类型。

然后您可以调用任何方法或调用任何您想要的命令。但这是另一回事了,你不应该在视图中硬编码 TabItems 并使用 Frame 元素来显示 Pages。请看这里的例子:

好的,我所做的是创建自定义选项卡控件。我将为此写出分步说明,然后您可以对其进行编辑。

  1. 右键单击您的解决方案select添加新项目
  2. 搜索自定义控件库
  3. 突出显示出现的 class 的名称,然后右键单击将其重命名为您想要的名称我将其命名为 MyTabControl.
  4. 将Prism.Wpf添加到新项目
  5. 在您需要的地方添加对新项目的引用。我只需要添加到主应用程序,但如果您有一个只有视图的单独项目,那么您也需要将其添加到该项目。
  6. TabControl 继承您的自定义控件喜欢:

    public class MyTabControl : TabControl

  7. 您会注意到项目中有一个 Themes 文件夹,您需要打开 Generic.xaml 并编辑它。它应该看起来像:

    TargetType="{x:Type local:MyTabControl}" BasedOn="{StaticResource {x:Type TabControl}}" 由于某些原因,这不会让我显示样式标签,但它们也需要在那里

  8. 请检查我从 Add A Command To Custom Control

    获得的代码
    public class MyTabControl : TabControl
     {
         static MyTabControl()
         {
             DefaultStyleKeyProperty.OverrideMetadata(typeof(MyTabControl), new FrameworkPropertyMetadata(typeof(MyTabControl)));
         }
    
         public static readonly DependencyProperty TabChangedCommandProperty = DependencyProperty.Register(
             "TabChangedCommand", typeof(ICommand), typeof(MyTabControl), 
             new PropertyMetadata((ICommand)null,
             new PropertyChangedCallback(CommandCallBack)));
    
         private static void CommandCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
         {
             var myTabControl = (MyTabControl)d;
             myTabControl.HookupCommands((ICommand) e.OldValue, (ICommand) e.NewValue);
         }
    
         private void HookupCommands(ICommand oldValue, ICommand newValue)
         {
            if (oldValue != null)
             {
                 RemoveCommand(oldValue, oldValue);
             }
             AddCommand(oldValue, oldValue);
         }
    
         private void AddCommand(ICommand oldValue, ICommand newCommand)
         {
             EventHandler handler = new EventHandler(CanExecuteChanged);
             var canExecuteChangedHandler = handler;
             if (newCommand != null)
             {
                 newCommand.CanExecuteChanged += canExecuteChangedHandler;
             }
    
         }
    
         private void CanExecuteChanged(object sender, EventArgs e)
         {
             if (this.TabChangedCommand != null)
             {
                 if (TabChangedCommand.CanExecute(null))
                 {
                     this.IsEnabled = true;
                 }
                 else
                 {
                     this.IsEnabled = false;
                 }
             }
         }
    
         private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
         {
             EventHandler handler = CanExecuteChanged;
             oldCommand.CanExecuteChanged -= handler;
         }
    
         public ICommand TabChangedCommand
         {
             get { return (ICommand) GetValue(TabChangedCommandProperty); }
             set { SetValue(TabChangedCommandProperty, value); }
         }
    
    
         public override void OnApplyTemplate()
         {
             base.OnApplyTemplate();
             this.SelectionChanged += OnSelectionChanged;
         }
    
         private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
         {
             if (TabChangedCommand != null)
             {
                 TabChangedCommand.Execute(null);
             }
         }
         }
    

您需要在 window 或用户控件中添加名称 space,例如:

xmlns:wpfCustomControlLibrary1="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1"

这是你的控件:

    <wpfCustomControlLibrary1:MyTabControl TabChangedCommand="{Binding TabChangedCommand}">
        <TabItem Header="View A"></TabItem>
        <TabItem Header="View B"></TabItem>
    </wpfCustomControlLibrary1:MyTabControl>

这就是我处理此类要求的方式: 查看:

    <Window.DataContext>
        <local:MainWIndowViewModel/>
    </Window.DataContext>
    <Grid>
        <TabControl Name="tc" ItemsSource="{Binding vms}">
            <TabControl.Resources>
                <DataTemplate DataType="{x:Type local:uc1vm}">
                    <local:UserControl1/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type local:uc2vm}">
                    <local:UserControl2/>
                </DataTemplate>
            </TabControl.Resources>
            <TabControl.ItemContainerStyle>
                <Style TargetType="TabItem">
                    <Setter Property="Header" Value="{Binding TabHeading}"/>
                </Style>
            </TabControl.ItemContainerStyle>
        </TabControl>
    </Grid>
</Window>

当它有一个 uc1vm 时,它将被模板化到视图中的 usercontrol1。
我绑定到一个视图模型的集合,它们都实现了一个接口,所以我确信我可以转换到那个并调用一个方法。

window 的主视图模型:

    private IDoSomething selectedVM;

    public IDoSomething SelectedVM
    {
        get { return selectedVM; }
        set
        {
            selectedVM = value;
            selectedVM.doit();
            RaisePropertyChanged();
        }
    }

    public ObservableCollection<IDoSomething> vms { get; set; } = new ObservableCollection<IDoSomething>
    {   new uc1vm(),
        new uc2vm()
    };

    public MainWIndowViewModel()
    {

    }

选择标签后,所选项目的 setter 将传递新值。强制转换并调用该方法。

我的界面非常简单,因为这只是说明性的:

public interface IDoSomething
{
    void doit();
}

一个示例视图模型,它也只是说明性的,并没有做太多事情:

public class uc1vm : IDoSomething
{
    public string TabHeading { get; set; } = "Uc1";
    public void doit()
    {
       // Your code goes here
    }
}

感谢您的所有意见,但我找到了替代解决方案。鉴于@mm8 提供的信息,我利用了 Loaded 事件,但在后面的代码中不需要任何代码。

我的解决方案

View 中,我想在每次用户选择包含它的 TabItem 时赋予这种执行方法的能力,我添加了以下代码:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding OnLoadedCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

然后简单地在 View 各自的 ViewModel 中实现了一个名为 OnLoadedCommandDelegateCommand。在该命令中,我调用了我想要的方法。

如果您发现此方法有任何问题,请发表评论!我选择尝试这个,因为它需要对我的代码进行最少的更改,但我可能会遗漏一些有关解决方案可能导致的问题的重要信息。