1

What I have:

  • using MVVM pattern
  • a view written in XAML
  • a command MyCommand in the ViewModel which gets called from several places in the view
  • a method DoSthInView that operates on the view, defined in codebehind

My Goal:

Whenever the command is executed, I want to call DoSthInView, no matter which control executed the command.

Question:

Since in MVVM the ViewModel does not know the View, I cannot call DoSthInView from the ViewModel. So how do call this code?

Own thoughts:

To be less abstract, this is my use case: We have one Button and one TextBox. The command takes the text which is currently in the TextBox and writes it somewhere into the model data. When this writing is done, I want to animate a green checkmark appearing and fading out (this is DoSthInView), so that the user gets a visual confirmation that the data was updated.

There are two ways of running the command:

  1. Click the Button
  2. Press "Enter" while the TextBox is focused

For the Button I know a way to call DoSthInView:

<Button Content="run command" Command="{Binding MyCommand}" Click={Binding DoSthInView}" />

For the TextBox, I have a KeyBinding to take the Enter key:

<TextBox>
    <TextBox.InputBindings>
        <KeyBinding Command="{Binding MyCommand}" Key="Enter" />
    </TextBox.InputBindings>
</TextBox>

But InputBindings seem not to support events, only commands. So here I have no idea how to call DoSthInView.

But even if I found a way to call DoSthInView from within the input binding (analog to the Button), it wouldn't feel right. I am looking for a way to say "whenever MyCommand is executed, run DoSthInView" So that not every caller of MyCommand has to care for it individually, but there is just one place to handle that. Maybe this can be done in the root FrameworkElement?

Kjara
  • 2,504
  • 15
  • 42
  • 2
    What about using a datatrigger to run the animation? Bind the data trigger to a property that change every time your modeldata text is modified, a boolean maybe. – Jose Oct 17 '16 at 08:06
  • 1
    May be you should raise an event in your command? And subscribe to it in your view (calling your function)? – Leonid Malyshev Oct 17 '16 at 09:33
  • Events will do the job but it's not MVVM. All the calculations must be done in ViewModel. The result of the calculations should be passed to View through bindings. That's the architecture. – Emad Oct 17 '16 at 09:38
  • 1
    Why can't you use `PreviewTextInput` for `TextBox` and trigger the storyboard? – Gopichandar Oct 17 '16 at 09:47
  • @LeonidMalyshev How can I do this? I am a beginner regarding event handling. Isn't it still a problem that the ViewModel does not know the View? – Kjara Oct 17 '16 at 10:54
  • @Kjara google something like "c# event handler demo" (eg https://msdn.microsoft.com/en-us/library/9aackb16(v=vs.110).aspx). And it's not a problem that "ViewModel does not know the View". – Leonid Malyshev Oct 17 '16 at 10:57
  • @Gopichandar The problem is, not every time "Enter" is hit the command is executed. Sometimes the text in the TextBox is invalid. Then MyCommandCanExecute returns false and MyCommandExecute is not called - and we do not want to trigger the animation in this case. – Kjara Oct 17 '16 at 13:19

4 Answers4

1

What you are asking for is possible. You need to implement RelayCommand. You can also see my other SO answer where there is an example.

Once you have RelayCommand implemented then you can do the following:

In 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();
}

In View:

<TextBox>
    <TextBox.InputBindings>
        <KeyBinding Command="{Binding Path=MyCommand}" CommandParameter="{Binding}" Key="Enter"/>
    </TextBox.InputBindings>
 </TextBox>

While it is not recommended to mix view and viewModel, there can be scenarios where otherwise is not possible. and sometimes it can be requirements. But again this is NOT recommended.

Nawed Nabi Zada
  • 2,819
  • 5
  • 29
  • 40
0

While I am still interested in an answer to my original question (calling codebehind-code after a command is executed), Kirenenko's advice helped me to solve my actual problem regarding the animation. This answer doesn't fit the original question any more because there is no codebehind-code (the animation is solely written in XAML, leaving no codebehind-code to execute). I still put it here because it is partially useful for me.

In the ViewModel, I have this:

...
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;
}
...

And here is the animation written in XAML and called by the 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>

Looks really stupid to set TriggerBool to true and then false again, but works...

Kjara
  • 2,504
  • 15
  • 42
0

Based on Leonid Malyshev's hint here is a quite clean solution using an event ("clean" regarding seperation of View and ViewModel):

ViewModel code:

public class MyViewModel
    {
    ...
    public event Action MyCommandExecuted;
    public DelegateCommand MyCommand; // consists of MyCommandExecute, MyCommandCanExecute
    ...
    private void MyCommandExecute()
    {
        ... // actual command code
        MyCommandExecuted.Invoke();
    }
    ...
}

View Codebehind:

public partial class MyView : Window
{
    public MyView(MyViewModel vm)
    {
        InitializeComponent();
        DataConext = vm;
        vm.MyCommandExecuted += DoSthInView();
    }
    ...
    private void DoSthInView()
    {
        ...
    }
    ...
}
Kjara
  • 2,504
  • 15
  • 42
  • This has a significant disadvantage: if you ever need to swap out the view model with a new instance of the viewmodel, you'll have to remove the handler(s) from the old command instance and reapply them to the new command instance. And if you preserve command instances between view model instances, you'll still have to update the new viewmodel instance with the original command instance. – Zev Spitz Nov 07 '19 at 18:38
-2

Usually you have a better more MVVM-ish way to these this thing but since I don't know your case I'll assume that there is no other way. So to this you need a dependency property in your view. A Boolean would be great and you handle it's on changed event and run DoSthInView in it. In your view model you set the value of this property and on changed gets called.

I can give you demonstration if you need. Also keep in mind that this is event driven coding which defiles MVVM. Try to use bindings and move your DoSthInView to ViewModel if possible.

Emad
  • 3,809
  • 3
  • 32
  • 44
  • What more MVVMish way do you have in mind? Can you please explain? If I have a dependency property in my View, how can I set this property in the ViewModel? The ViewModel does not know the View. For the same reason, it is not possible to move `DoSthInView` into the ViewModel. – Kjara Oct 17 '16 at 09:27
  • If you'd been more clear with your question I could give more help. But I can give you an example for what I mean. Suppose you want to change a color in your view. What do you do? You bind the color to a viewmodel property. This color that you bind is a dependency property. You can create those in your view and handle their changes. – Emad Oct 17 '16 at 09:31
  • The problem is, I have an animation. I don't see neither a way nor a reason to define this animation in the ViewModel - the ViewModel, as I understand, is the link between Model (data) and View (UI). It takes data from the model and prepares it for the View to display. And it receives the user input from the UI to process it and make changes to the model. It is solely and adapter layer between Model and View. Where does a View-only thing, like an animation, fit into this? If putting the animation into the ViewModel is "the right thing to do", then Buttons and TextBoxes should also go in there. – Kjara Oct 17 '16 at 11:03