在使用 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
来实现这一点。
在此先感谢大家的帮助。
例如,您可以处理 Page
的 Loaded
事件,以便在最初加载视图后调用方法或调用视图模型的命令:
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
中处理这个问题。您将 TabControl
的 SelectedItem
属性 绑定到 MainViewModel
的 属性 并将此 属性 设置为 [=17] 的实例=] 或 ViewModel2
,具体取决于您要显示的视图类型。
然后您可以调用任何方法或调用任何您想要的命令。但这是另一回事了,你不应该在视图中硬编码 TabItems
并使用 Frame
元素来显示 Pages
。请看这里的例子:
好的,我所做的是创建自定义选项卡控件。我将为此写出分步说明,然后您可以对其进行编辑。
- 右键单击您的解决方案select添加新项目
- 搜索自定义控件库
- 突出显示出现的 class 的名称,然后右键单击将其重命名为您想要的名称我将其命名为
MyTabControl.
- 将Prism.Wpf添加到新项目
- 在您需要的地方添加对新项目的引用。我只需要添加到主应用程序,但如果您有一个只有视图的单独项目,那么您也需要将其添加到该项目。
从 TabControl
继承您的自定义控件喜欢:
public class MyTabControl : TabControl
您会注意到项目中有一个 Themes 文件夹,您需要打开 Generic.xaml
并编辑它。它应该看起来像:
TargetType="{x:Type local:MyTabControl}" BasedOn="{StaticResource {x:Type TabControl}}"
由于某些原因,这不会让我显示样式标签,但它们也需要在那里
请检查我从 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
中实现了一个名为 OnLoadedCommand
的 DelegateCommand
。在该命令中,我调用了我想要的方法。
如果您发现此方法有任何问题,请发表评论!我选择尝试这个,因为它需要对我的代码进行最少的更改,但我可能会遗漏一些有关解决方案可能导致的问题的重要信息。
我已经尝试实现它一段时间了,但到目前为止还没有做到,尽管我觉得这应该很容易。
困难来自于我已经使用 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
来实现这一点。
在此先感谢大家的帮助。
例如,您可以处理 Page
的 Loaded
事件,以便在最初加载视图后调用方法或调用视图模型的命令:
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
中处理这个问题。您将 TabControl
的 SelectedItem
属性 绑定到 MainViewModel
的 属性 并将此 属性 设置为 [=17] 的实例=] 或 ViewModel2
,具体取决于您要显示的视图类型。
然后您可以调用任何方法或调用任何您想要的命令。但这是另一回事了,你不应该在视图中硬编码 TabItems
并使用 Frame
元素来显示 Pages
。请看这里的例子:
好的,我所做的是创建自定义选项卡控件。我将为此写出分步说明,然后您可以对其进行编辑。
- 右键单击您的解决方案select添加新项目
- 搜索自定义控件库
- 突出显示出现的 class 的名称,然后右键单击将其重命名为您想要的名称我将其命名为
MyTabControl.
- 将Prism.Wpf添加到新项目
- 在您需要的地方添加对新项目的引用。我只需要添加到主应用程序,但如果您有一个只有视图的单独项目,那么您也需要将其添加到该项目。
从
TabControl
继承您的自定义控件喜欢:public class MyTabControl : TabControl
您会注意到项目中有一个 Themes 文件夹,您需要打开
Generic.xaml
并编辑它。它应该看起来像:TargetType="{x:Type local:MyTabControl}" BasedOn="{StaticResource {x:Type TabControl}}"
由于某些原因,这不会让我显示样式标签,但它们也需要在那里请检查我从 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
中实现了一个名为 OnLoadedCommand
的 DelegateCommand
。在该命令中,我调用了我想要的方法。
如果您发现此方法有任何问题,请发表评论!我选择尝试这个,因为它需要对我的代码进行最少的更改,但我可能会遗漏一些有关解决方案可能导致的问题的重要信息。