danbroooks
danbroooks

Reputation: 2810

MVVM mixing Behavior and RelayCommand not working as expected

I'm trying to set up some behaviors to control windows in an MVVM application.

My thought is behaviors can be set decoratively in XAML and exist in their own class, allowing for them in some cases to be reused in many different views.

I also have a menu item that is bound to a RelayCommand. Both of these work fine on their own, however when I try to combine the two things don't work as expected:

MainView.xaml

<Window x:Class="CaseManager.Views.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:VM="clr-namespace:CaseManager.ViewModels;assembly=CaseManager.ViewModels"
        xmlns:local="clr-namespace:CaseManager.Views"
        local:ExitBehavior.ExitWhen="{Binding ExitFlag}"
        Title="Main Window" Height="350" Width="525">

    <Window.DataContext>
        <VM:MainViewModel />
    </Window.DataContext>

    <Grid>

        <Menu Height="20" VerticalAlignment="Top">
            <MenuItem Header="_File">
                <MenuItem Header="_Exit" Command="{Binding ExitCommand}" />
            </MenuItem>
        </Menu>

    </Grid>
</Window>

MainViewModel.cs

namespace CaseManager.ViewModels
{
    using System;
    using System.Collections.Generic;

    using Commands;

    public class MainViewModel : ViewModelBase
    {
        # region RelayCommands
        private RelayCommand _exitCommand;
        public RelayCommand ExitCommand
        {
            get { return _exitCommand; }
            private set { _exitCommand = value; }
        }
        #endregion

        private bool _exitFlag;
        public bool ExitFlag { 
            get { return _exitFlag; } 
            private set { _exitFlag = value; } 
        }

        public MainViewModel()
        {
            ExitCommand = new RelayCommand(ExitCommand_Execute);
            ExitFlag = false;
        }

        private void ExitCommand_Execute(object obj)
        {
            System.Console.WriteLine("Command executed");
            ExitFlag = true;
        }
    }
}

ExitCommand.cs

namespace CaseManager.Views
{
    using System;
    using System.Windows;

    public static class ExitBehavior
    {
        public static bool GetExitWhen(DependencyObject obj)
        {
            return (bool)obj.GetValue(ExitWhenProperty);
        }

        public static void SetExitWhen(DependencyObject obj, bool value)
        {
            obj.SetValue(ExitWhenProperty, value);
        }

        public static readonly DependencyProperty ExitWhenProperty =
            DependencyProperty.RegisterAttached(
                "ExitWhen", typeof(bool), typeof(ExitBehavior),
                new UIPropertyMetadata(OnExitWhenChanged));

        static void OnExitWhenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            System.Console.WriteLine("Behavior executed");
            Environment.Exit(0);
        }
    }
}

Running the code above as is will output 'Command executed' in the console when I select exit in the menu. So I know the relay command is working fine. However the behavior is not.

If I change the constructor of my MainViewModel to set ExitFlag to true, the program will launch, print 'Behavior executed' to console, and then quit.

Both the ExitBehavior and RelayCommand work on their own, however when I try to trigger the ExitBehavior by setting the ExitFlag in my relay command, they cannot be used together. Am i missing something here?

For brevity here is RelayCommand.cs:

namespace CaseManager.Commands
{
    using System;
    using System.Windows.Input;

    public class RelayCommand : ICommand
    {
        #region Fields

        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        #endregion

        #region Constructors

        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }
        #endregion

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

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

        #endregion
    }
}

Upvotes: 0

Views: 397

Answers (2)

Anatoliy Nikolaev
Anatoliy Nikolaev

Reputation: 22702

Firtsly, as I understood from your code, here:

local:CloseBehavior.CloseWhen="{Binding CloseFlag}"

there must be a property ExitFlag.

Secondly, this property should call OnPropertyChanged method from INotifyPropertyChanged interface to update the property and in XAML should be as:

local:CloseBehavior.CloseWhen="{Binding Path=ExitFlag, Mode=TwoWay}"

Upvotes: 0

Dennis
Dennis

Reputation: 37790

You have to notify the binding engine, that your property ExitFlag was changed. Assuming, that ViewModelBase implements INotifyPropertyChanged somehow:

       public bool ExitFlag 
       { 
            get { return _exitFlag; } 
            private set 
            { 
               _exitFlag = value; 
               OnPropertyChanged("ExitFlag");
            } 
        }

where OnPropertyChanged is a method, that raises INPC.PropertyChanged event.

Upvotes: 3

Related Questions