MVVM:运行 调用命令后的只读代码
MVVM: run view-only code after calling command
我有:
- 使用 MVVM 模式
- 在XAML
中编写的视图
- ViewModel 中的命令
MyCommand
从视图中的多个位置调用
- 在代码隐藏中定义的对视图进行操作的方法
DoSthInView
我的目标:
每当命令执行时,我想调用DoSthInView
,无论哪个控件执行命令。
问题:
由于在 MVVM 中,ViewModel 不了解 View,因此我无法从 ViewModel 调用 DoSthInView
。那么这段代码怎么调用呢?
自己的想法:
为了不那么抽象,这是我的用例:我们有一个按钮和一个文本框。该命令获取当前位于 TextBox 中的文本并将其写入模型数据中的某处。写完这篇文章后,我想为一个绿色复选标记的出现和淡出设置动画(这是 DoSthInView
),以便用户可以直观地确认数据已更新。
运行命令有两种方式:
- 单击按钮
- 在文本框获得焦点时按 "Enter"
对于按钮,我知道调用 DoSthInView
:
的方法
<Button Content="run command" Command="{Binding MyCommand}" Click={Binding DoSthInView}" />
对于 TextBox,我有一个 KeyBinding 来获取 Enter 键:
<TextBox>
<TextBox.InputBindings>
<KeyBinding Command="{Binding MyCommand}" Key="Enter" />
</TextBox.InputBindings>
</TextBox>
但是InputBindings好像不支持事件,只支持命令。所以在这里我不知道如何调用 DoSthInView
.
但即使我找到了一种从输入绑定(类似于按钮)中调用 DoSthInView
的方法,它也感觉不对。我正在寻找一种表达 "whenever MyCommand
is executed, run DoSthInView
" 的方式,以便并非 MyCommand
的每个调用者都必须单独处理它,但只有一个地方可以处理它。也许这可以在根 FrameworkElement 中完成?
通常你有更好的 MVVM 风格的方法来处理这些事情,但由于我不知道你的情况,我会假设没有其他方法。
因此,您需要在视图中依赖 属性。布尔值会很棒,您可以在更改事件和 运行 DoSthInView 中处理它。在您的视图模型中,您设置此 属性 的值并在更改时调用。
如果你需要,我可以给你演示。还要记住,这是污染 MVVM 的事件驱动编码。如果可能,请尝试使用绑定并将您的 DoSthInView 移动到 ViewModel。
虽然我仍然对我的原始问题的答案感兴趣(在执行命令后调用代码隐藏代码),但 Kirenenko 的建议帮助我解决了我关于动画的实际问题。这个答案不再适合原始问题,因为没有代码隐藏代码(动画完全用 XAML 编写,没有代码隐藏代码可以执行)。我仍然把它放在这里,因为它对我有部分用处。
在 ViewModel 中,我有这个:
...
private bool _triggerBool;
public bool TriggerBool
{
get { return _triggerBool; }
set
{
if (_triggerBool != value)
{
_triggerBool = value;
NotifyPropertyChanged(nameof(TriggerBool));
}
}
}
...
public DelegateCommand MyCommand; // consists of MyCommandExecute and MyCommandCanExecute
...
public void MyCommandExecute()
{
... // actual command code
TriggerBool = true;
TriggerBool = false;
}
...
这是在 XAML 中编写并由 DataTrigger 调用的动画:
<Image Source="myGreenCheckmark.png" Opacity="0">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding TriggerBool}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="1" To="0" Duration="0.0:0:0.750"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
将 TriggerBool
设置为 true
,然后再次设置 false
看起来真的很愚蠢,但是有效...
你所要求的是可能的。您需要实施 RelayCommand。
也可以看我的另一个里面有例子
实现 RelayCommand 后,您可以执行以下操作:
在 ViewModel 中:
public ICommand MyCommand { get; set; }
public MyViewModel()
{
MyCommand = new RelayCommand(MyCommand_Execute);
}
private void MyCommand_Execute(object sender)
{
var myView = sender as MyView;
myView?.DoSthInView();
}
在视图中:
<TextBox>
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=MyCommand}" CommandParameter="{Binding}" Key="Enter"/>
</TextBox.InputBindings>
</TextBox>
虽然不建议混合使用 view 和 viewModel,但在某些情况下是不可能的。有时它可能是要求。但同样不推荐这样做。
根据 Leonid Malyshev 的提示,这是一个使用事件的非常干净的解决方案("clean" 关于 View 和 ViewModel 的分离):
视图模型代码:
public class MyViewModel
{
...
public event Action MyCommandExecuted;
public DelegateCommand MyCommand; // consists of MyCommandExecute, MyCommandCanExecute
...
private void MyCommandExecute()
{
... // actual command code
MyCommandExecuted.Invoke();
}
...
}
查看代码隐藏:
public partial class MyView : Window
{
public MyView(MyViewModel vm)
{
InitializeComponent();
DataConext = vm;
vm.MyCommandExecuted += DoSthInView();
}
...
private void DoSthInView()
{
...
}
...
}
我有:
- 使用 MVVM 模式
- 在XAML 中编写的视图
- ViewModel 中的命令
MyCommand
从视图中的多个位置调用 - 在代码隐藏中定义的对视图进行操作的方法
DoSthInView
我的目标:
每当命令执行时,我想调用DoSthInView
,无论哪个控件执行命令。
问题:
由于在 MVVM 中,ViewModel 不了解 View,因此我无法从 ViewModel 调用 DoSthInView
。那么这段代码怎么调用呢?
自己的想法:
为了不那么抽象,这是我的用例:我们有一个按钮和一个文本框。该命令获取当前位于 TextBox 中的文本并将其写入模型数据中的某处。写完这篇文章后,我想为一个绿色复选标记的出现和淡出设置动画(这是 DoSthInView
),以便用户可以直观地确认数据已更新。
运行命令有两种方式:
- 单击按钮
- 在文本框获得焦点时按 "Enter"
对于按钮,我知道调用 DoSthInView
:
<Button Content="run command" Command="{Binding MyCommand}" Click={Binding DoSthInView}" />
对于 TextBox,我有一个 KeyBinding 来获取 Enter 键:
<TextBox>
<TextBox.InputBindings>
<KeyBinding Command="{Binding MyCommand}" Key="Enter" />
</TextBox.InputBindings>
</TextBox>
但是InputBindings好像不支持事件,只支持命令。所以在这里我不知道如何调用 DoSthInView
.
但即使我找到了一种从输入绑定(类似于按钮)中调用 DoSthInView
的方法,它也感觉不对。我正在寻找一种表达 "whenever MyCommand
is executed, run DoSthInView
" 的方式,以便并非 MyCommand
的每个调用者都必须单独处理它,但只有一个地方可以处理它。也许这可以在根 FrameworkElement 中完成?
通常你有更好的 MVVM 风格的方法来处理这些事情,但由于我不知道你的情况,我会假设没有其他方法。 因此,您需要在视图中依赖 属性。布尔值会很棒,您可以在更改事件和 运行 DoSthInView 中处理它。在您的视图模型中,您设置此 属性 的值并在更改时调用。
如果你需要,我可以给你演示。还要记住,这是污染 MVVM 的事件驱动编码。如果可能,请尝试使用绑定并将您的 DoSthInView 移动到 ViewModel。
虽然我仍然对我的原始问题的答案感兴趣(在执行命令后调用代码隐藏代码),但 Kirenenko 的建议帮助我解决了我关于动画的实际问题。这个答案不再适合原始问题,因为没有代码隐藏代码(动画完全用 XAML 编写,没有代码隐藏代码可以执行)。我仍然把它放在这里,因为它对我有部分用处。
在 ViewModel 中,我有这个:
...
private bool _triggerBool;
public bool TriggerBool
{
get { return _triggerBool; }
set
{
if (_triggerBool != value)
{
_triggerBool = value;
NotifyPropertyChanged(nameof(TriggerBool));
}
}
}
...
public DelegateCommand MyCommand; // consists of MyCommandExecute and MyCommandCanExecute
...
public void MyCommandExecute()
{
... // actual command code
TriggerBool = true;
TriggerBool = false;
}
...
这是在 XAML 中编写并由 DataTrigger 调用的动画:
<Image Source="myGreenCheckmark.png" Opacity="0">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding TriggerBool}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="1" To="0" Duration="0.0:0:0.750"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
将 TriggerBool
设置为 true
,然后再次设置 false
看起来真的很愚蠢,但是有效...
你所要求的是可能的。您需要实施 RelayCommand。
也可以看我的另一个
实现 RelayCommand 后,您可以执行以下操作:
在 ViewModel 中:
public ICommand MyCommand { get; set; }
public MyViewModel()
{
MyCommand = new RelayCommand(MyCommand_Execute);
}
private void MyCommand_Execute(object sender)
{
var myView = sender as MyView;
myView?.DoSthInView();
}
在视图中:
<TextBox>
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=MyCommand}" CommandParameter="{Binding}" Key="Enter"/>
</TextBox.InputBindings>
</TextBox>
虽然不建议混合使用 view 和 viewModel,但在某些情况下是不可能的。有时它可能是要求。但同样不推荐这样做。
根据 Leonid Malyshev 的提示,这是一个使用事件的非常干净的解决方案("clean" 关于 View 和 ViewModel 的分离):
视图模型代码:
public class MyViewModel
{
...
public event Action MyCommandExecuted;
public DelegateCommand MyCommand; // consists of MyCommandExecute, MyCommandCanExecute
...
private void MyCommandExecute()
{
... // actual command code
MyCommandExecuted.Invoke();
}
...
}
查看代码隐藏:
public partial class MyView : Window
{
public MyView(MyViewModel vm)
{
InitializeComponent();
DataConext = vm;
vm.MyCommandExecuted += DoSthInView();
}
...
private void DoSthInView()
{
...
}
...
}