0

So, I have this command call CloseCommand in my Commands folder. I also have a WindowViewModel class in my ViewModels folder. Here's the content inside the WindowViewModel:

using Test.Commands;

namespace Test.ViewModels
{
    public class WindowViewModel
    {

        #region Window
        public MainWindow mainWindow { get; set; }
        public CloseCommand CloseCommand{ get; set; } = new CloseCommand();
        #endregion

        public WindowViewModel(MainWindow mainWindow)
        {
            this.mainWindow = mainWindow;
            Test = "Hello";
            CloseCommand.mainWindow = this.mainWindow;
        }
        public WindowViewModel() { }

    }
}

Here's the content inside the CloseCommand class:

using System;
using System.Windows;
using System.Windows.Input;

namespace Test.Commands
{
    public class CloseCommand: ICommand
    {
        public event EventHandler CanExecuteChanged;
        public MainWindow mainWindow { get; set; }

        public CloseCommand(MainWindow mainWindow)
        {
            this.mainWindow = mainWindow;
        }

        public CloseCommand() { }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            MessageBox.Show("Press OK to close");
            mainWindow.Close();
        }
    }
}

Here's the content inside the MainWindow.xaml:

    <Window.Resources>
        <!-- I have already defined "commands" when defining Window-->
        <commands:CloseCommand x:Key="CloseCommand"/>
    </Window.Resources>

    <Grid>
        <Button Content="{Binding Test}" Background="Blue" Foreground="White" FontSize="50" Command="{StaticResource CloseCommand}"/>
    </Grid>

Now, when I run the program and press my button, it display the MessageBox I had defined in the CloseCommand.Execute method, but after I press the OK button in the MessageBox, it gives me an error:

mainWindow property is null.

I expect this problem happens because the CloseCommand is converted to an ICommand when I defined it in my MainWindow.xaml and in the Command property of the Button. So how do I fix this problem? Is my expectations right? Please correct me.

Marc2001
  • 281
  • 1
  • 3
  • 12

3 Answers3

0

You are not passing MainWindow to the ICommand, hence the error. To pass MainWindow , you have to use Binding, and for that to happen your ICommand must be a DependencyObject . See this question.

AnjumSKhan
  • 9,647
  • 1
  • 26
  • 38
0

Your sample program contains two instances of CloseCommand:

  • the one in Window.Resources
  • the one constructed in WindowViewModel

Therefore, the command that you use for the Button is not the same one that you initialize in WindowViewModel.

A quick fix would be to delete the CloseCommand in Window.Resources and to change the Command property in the Button to use a binding, not a StaticResource. Furthermore, pass the window reference to the CommandParameter parameter.

Here is an example, based on your code.

MainWindow.xaml:

<Window x:Class="WpfApplication11.MainWindow"
        x:Name="theWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication11"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        d:DataContext="{d:DesignInstance d:Type=local:WindowViewModel}">
    <Grid>
        <Button Content="{Binding Test}" Background="Blue" Foreground="White" FontSize="50" 
                Command="{Binding CloseCommand}"
                CommandParameter="{Binding ElementName=theWindow}"/>
    </Grid>
</Window>

MainWindow.xaml.cs:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;

namespace WpfApplication11
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new WindowViewModel(this);
        }
    }

    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class WindowViewModel : ViewModelBase
    {
        private string _test;

        public WindowViewModel(MainWindow mainWindow)
        {
            Test = "Hello";
        }

        public string Test
        {
            get
            {
                return _test;
            }
            set
            {
                if (_test != value)
                {
                    _test = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public CloseCommand CloseCommand { get; set; } = new CloseCommand();
    }

    public class CloseCommand : ICommand
    {
        public event EventHandler CanExecuteChanged;
        public MainWindow mainWindow { get; set; }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            MessageBox.Show("Press OK to close");
            ((Window)parameter).Close();
        }
    }
}

Side note:

If your goal, however, is to write an application that follows the MVVM design philosophy I would not recommend to use the above approach. In a typical MVVM application, your ViewModel would have either an event or a callback (= delegate) called something like CloseWindow which is triggered by the ViewModel and handled by the View. One example of that can be found here: Closing a WPF Window using MVVM and minimal code-behind.

FrankM
  • 1,007
  • 6
  • 15
  • Is it necessary to make ViewModelBase and make it implement INotifyPropertyChanged? – Marc2001 Jul 09 '18 at 00:56
  • For this particular example, it would not be strictly necessary. But if you later want to change the `Test` property after creation of the `WindowViewModel`, then the setter of `Test` property needs to raise the `INotifyPropertyChanged.PropertyChanged` event. – FrankM Jul 09 '18 at 04:38
0

@FrankM

I've got it working but it's not the same way you did it.

In CloseCommand:

public class CloseCommand : ICommand {
      public event EventHandler CanExecuteChanged;
      public Action<object> ExecuteDelegate;
      public bool CanExecuteBool;

      public CloseCommand(bool CanExecute, Action<object> Execute) {
            CanExecuteBool = CanExecute;
            ExecuteDelegate = Execute;
      }

      public bool CanExecute(object parameter) {
            return CanExecuteBool;
      }

      public void Execute(object parameter) {
            ExecuteDelegate(parameter);
      }
}

In WindowViewModel:

public class WindowViewModel {
      public CloseCommand CloseCommand { get; set; }
      public bool CanCloseWindow { get; set; } = true;
      private bool CanExecuteCloseCommand () {
          return CanCloseWindow;
      }
      private void ExecuteCloseCommand(object parameter) {
          Window window = parameter as Window;
          window.Close();
      }
      // Constructor
      public WindowViewModel() {
           CloseCommand = new CloseCommand(CanExecuteCloseCommand(), ExecuteCloseCommand);
      }
}

This works but I got some questions. What's a better approach? Yours or mine? What's the pros and cons of each one?

Thanks for your answer!

Marc2001
  • 281
  • 1
  • 3
  • 12