【问题标题】:How to run a method every time a TabItem is selected, in an MVVM application using Prism如何在使用 Prism 的 MVVM 应用程序中每次选择 TabItem 时运行方法
【发布时间】:2018-05-03 15:08:12
【问题描述】:

我一直在尝试实现这一点,但到目前为止还没有做到,尽管我觉得这应该很容易。

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

我的实现

我有三个视图及其各自的视图模型(使用 Prism 的 AutoWireViewModel 方法连接)。 MainView 有一个TabControl 和两个TabItems,每个女巫都包含一个Frame 容器,其中Source 设置为另外两个Views 之一。以下代码摘自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 来实现这一点。

提前感谢所有帮助。

【问题讨论】:

  • 可以发xaml吗?和ViewModels之一?
  • 方法在哪里定义?在视图还是视图模型中?
  • @3xGuy 我已经添加了xaml 代码,但是 ViewModel 代码很长。你到底想要什么?
  • 谢谢,我会研究一下给我一些。
  • tabcontrol 是一种选择器。 msdn.microsoft.com/en-us/library/… 当您选择一个 tabitem 时,您正在选择一个项目。您可以将 selecteditem 绑定到视图模型中的属性,并为此在 setter 中执行操作。您还可以绑定和模板来生成标签。

标签: c# wpf mvvm prism


【解决方案1】:

例如,您可以处理 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 的属性,并将此属性设置为ViewModel2ViewModel2 的实例,具体取决于您要显示的视图类型。

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

Selecting TabItem in TabControl from ViewModel

【讨论】:

  • 嘿马格努斯。我不太确定页面是否会在您第二次选择 tabitem 时再次加载。不过我很少使用页面。
  • @Andy:如果你注意我的示例代码,你会发现我在 Loaded 事件发生时实际上取消了事件处理程序。这意味着该命令只会在第一次选择选项卡时调用一次。如果您想在每次选择选项卡时调用该命令,您可以简单地删除这行代码。是的,这应该可以工作,因为 TabControl 实际上会卸载一个被取消选择的选项卡,然后在再次选择它时重新加载它。
  • 这似乎是一个很好的解决方案,非常简单。但是由于这是在后面的代码中完成的,它不会破坏 MVVM 模式吗?您的评论指出“TabControl 实际上卸载了一个被取消选择的选项卡,然后在再次选择它时重新加载它”非常有用,谢谢 :)
  • @r_laezza:这不会破坏 MVVM 模式。视图的代码隐藏与 XAML 属于同一类,因此无论您是从代码隐藏还是 XAML 执行命令都无关紧要。在代码隐藏中执行此操作可让您在命令被调用一次后取消订阅事件处理程序。
【解决方案2】:

好的,我所做的是创建一个自定义选项卡控件。我会写出一步一步的说明,然后你可以添加编辑。

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

    公共类 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);
             }
         }
         }
    

您需要在窗口或用户控件中添加名称空间,例如:

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

这是你的控制:

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

【讨论】:

    【解决方案3】:

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

        <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。
    我正在绑定到一个视图模型的集合,这些视图模型都实现了一个接口,所以我确定我可以转换为该接口并调用一个方法。

    窗口的主视图模型:

        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()
        {
    
        }
    

    当一个选项卡被选中时,所选项目的设置器将被传递新值。转换它并调用该方法。

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

    public interface IDoSomething
    {
        void doit();
    }
    

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

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

    【讨论】:

      【解决方案4】:

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

      我的解决方案

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

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

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

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

      【讨论】:

      • 如果您需要/想要一个选项卡控件(而不是您手动导航的区域),这绝对是要走的路。
      • 这将在每次选择选项卡时调用该命令。如果这不是问题,您可以在 XAML 中使用交互触发器。但是,从视图的代码隐藏以编程方式执行命令并不违反 MVVM 模式。 MVVM不是要从视图中消除与 view 相关的代码。这是关于关注点分离。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-30
      • 2018-12-03
      • 2021-03-18
      • 1970-01-01
      • 2015-02-12
      • 2012-02-11
      相关资源
      最近更新 更多