Custom Command Behavior

Topics: Prism v2 - Silverlight 3
Aug 19, 2009 at 3:57 PM

Im using Prism to create a Silverlight application (SL3). I wanted to make some custom command behaviors to supplement Prism's built in command behavior which is at current just click on a button. I downloaded the following code snippet

http://www.silverlightplayground.org/post/2009/07/09/A-code-snippet-to-quickly-write-Prism-commands.aspx:

which, to the best of my knowledge appears to generate the correct code. Or it is at least similar to what is introduced here:

http://development-guides.silverbaylabs.org/Video/Prism-Commands

At any rate here is the code that I end up with to implement the mouse out command behavior

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Practices.Composite.Presentation.Commands;

namespace MyProject.Infrastructure.Commands
{
    public class MouseOutCommandBehavior : CommandBehaviorBase<Control>
    {
        public MouseOutCommandBehavior(Control targetObject)
            : base(targetObject)
        {
            targetObject.MouseLeave += (s, e) => base.ExecuteCommand();
        }
    }

    public static class MouseOut
    {
        public static readonly DependencyProperty MouseOutBehaviorProperty =
            DependencyProperty.RegisterAttached(
                "MouseOutBehaviorProperty", typeof(MouseOutCommandBehavior),
                typeof(MouseOutCommandBehavior), null);

        #region CommandProperty

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached(
                "Command", typeof(ICommand), typeof(MouseOut),
                new PropertyMetadata(CommandProperty_Changed));

        public static ICommand GetCommand(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(CommandProperty);
        }

        public static void SetCommand(DependencyObject obj, ICommand value)
        {
            obj.SetValue(CommandProperty, value);
        }

        private static void CommandProperty_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            Control targetObject = dependencyObject as Control;

            if (targetObject != null)
                GetOrCreateBehavior(targetObject).Command = e.NewValue as ICommand;
        }

        #endregion

        #region CommandParameterProperty

        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.RegisterAttached(
                "CommandParameter", typeof(object),
                typeof(MouseOut), new PropertyMetadata(CommandParameterProperty_Changed));

        public static ICommand GetCommandParameter(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(CommandParameterProperty);
        }

        public static void SetCommandParameter(DependencyObject obj, ICommand value)
        {
            obj.SetValue(CommandParameterProperty, value);
        }

        private static void CommandParameterProperty_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            Control targetObject = dependencyObject as Control;

            if (targetObject != null)
                GetOrCreateBehavior(targetObject).CommandParameter = e.NewValue;
        }

        #endregion

        private static MouseOutCommandBehavior GetOrCreateBehavior(Control targetObject)
        {
            MouseOutCommandBehavior behavior = targetObject.GetValue(MouseOutBehaviorProperty) as MouseOutCommandBehavior;

            if (behavior == null)
            {
                behavior = new MouseOutCommandBehavior(targetObject);
                targetObject.SetValue(MouseOutBehaviorProperty, behavior);
            }

            return behavior;
        }
    }
}


And my XAML is as follows
<UserControl x:Class="MyProject.View.NavigationWidgetModule.NavigationWidgetControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:Command="clr-namespace:Microsoft.Practices.Composite.Presentation.Commands;assembly=Microsoft.Practices.Composite.Presentation"
    xmlns:OtherCommand="clr-namespace:MyProject.Infrastructure.Commands;assembly=MyProject.Infrastructure"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White" >
        <Button OtherCommand:MouseOut.Command="{Binding MouseOutCommand}" />
    </Grid>
</UserControl>

but everytime I run this I get a AG_E_PARSER_BAD_PROPERTY_VALUE error. Now if I replace the XAML with this (ie the built in Click command)


<UserControl x:Class="MyProject.View.NavigationWidgetModule.NavigationWidgetControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:Command="clr-namespace:Microsoft.Practices.Composite.Presentation.Commands;assembly=Microsoft.Practices.Composite.Presentation"
    xmlns:OtherCommand="clr-namespace:MyProject.Infrastructure.Commands;assembly=MyProject.Infrastructure"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White" >
        <Button Command:Click.Command="{Binding MouseOutCommand}" />
    </Grid>
</UserControl>
... it works properly. Is the command behavior code correct or am I doing something wrong?

Aug 19, 2009 at 4:25 PM

I am not sure if this is completely the fix but the following look wrong:

public static readonly DependencyProperty MouseOutBehaviorProperty =
            DependencyProperty.RegisterAttached(
                "MouseOutBehaviorProperty", typeof(MouseOutCommandBehavior),
                typeof(MouseOutCommandBehavior), null);

I think it should be:

public static readonly DependencyProperty MouseOutCommandBehaviorProperty =
            DependencyProperty.RegisterAttached(
                "MouseOutCommandBehavior", typeof(MouseOutCommandBehavior),
                typeof(MouseOut), null);

There are some name changes for consistency as well and so the code likely will need to be changed slightly for compilation.

HTH

Aug 19, 2009 at 4:34 PM

Hey Fred,

Thanks for the input! I tried it and it still failed at the same point. I've also tried the code suggested here:

http://compositewpf.codeplex.com/Thread/View.aspx?ThreadId=66164

Changing FrameworkPropertyMetadata to PropertyMetadata but I still get the exact same error. Is there a bug in SL3 or something?

Aug 19, 2009 at 5:36 PM

Hi

I created the following attached behavior using this snippet. I binding it to a button and moving in/out of it and it worked great.

public static class MouseOut
    {
        private static readonly DependencyProperty MouseOutCommandBehaviorProperty
            = DependencyProperty.RegisterAttached(
            "MouseOutCommandBehavior",
            typeof(MouseOutCommandBehavior),
            typeof(MouseOut),
            null);

        public static readonly DependencyProperty CommandProperty
            = DependencyProperty.RegisterAttached(
            "Command",
            typeof(ICommand),
            typeof(MouseOut),
            new PropertyMetadata(OnSetCommandCallback));

        public static readonly DependencyProperty CommandParameterProperty
            = DependencyProperty.RegisterAttached(
           "CommandParameter",
           typeof(object),
           typeof(MouseOut),
           new PropertyMetadata(OnSetCommandParameterCallback));

        public static ICommand GetCommand(Control control)
        {
            return control.GetValue(CommandProperty) as ICommand;
        }

        public static void SetCommand(Control control, ICommand command)
        {
            control.SetValue(CommandProperty, command);
        }

        public static void SetCommandParameter(Control control, object parameter)
        {
            control.SetValue(CommandParameterProperty, parameter);
        }

        public static object GetCommandParameter(Control control)
        {
            return control.GetValue(CommandParameterProperty);
        }

        private static void OnSetCommandCallback
            (DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            Control control = dependencyObject as Control;
            if (control != null)
            {
                MouseOutCommandBehavior behavior = GetOrCreateBehavior(control);
                behavior.Command = e.NewValue as ICommand;
            }
        }

        private static void OnSetCommandParameterCallback
            (DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            Control control = dependencyObject as Control;
            if (control != null)
            {
                MouseOutCommandBehavior behavior = GetOrCreateBehavior(control);
                behavior.CommandParameter = e.NewValue;
            }
        }

        private static MouseOutCommandBehavior GetOrCreateBehavior(Control control)
        {
            MouseOutCommandBehavior behavior =
                control.GetValue(MouseOutCommandBehaviorProperty) as MouseOutCommandBehavior;
            if (behavior == null)
            {
                behavior = new MouseOutCommandBehavior(control);
                control.SetValue(MouseOutCommandBehaviorProperty, behavior);
            }
            return behavior;
        }
    }

    public class MouseOutCommandBehavior : CommandBehaviorBase<Control>
    {
        public MouseOutCommandBehavior(Control control)
            : base(control)
        {
            control.MouseLeave += OnMouseLeave;
        }

        private void OnMouseLeave(object sender, RoutedEventArgs e)
        {
            ExecuteCommand();
        }
    }

Then in the XAML I used the following code to perform the binding (mou is the namespace where the class with attached behavior is):

<Button mou:MouseOut.Command="{Binding Path=MouseOutCommand}"/>

Please let me know if this helps

Damian Schenkelman
http://blogs.southworks.net/dschenkelman

Aug 19, 2009 at 6:14 PM

Hey Damian,

Even using your code directly still doesnt work for me.

Here is my XAML

<UserControl x:Class="MyProject.View.NavigationWidgetModule.NavigationWidgetControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Command="clr-namespace:Microsoft.Practices.Composite.Presentation.Commands;assembly=Microsoft.Practices.Composite.Presentation"
    xmlns:OtherCommand="clr-namespace:MyProject.Infrastructure.Commands;assembly=MyProject.Infrastructure"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White" >
        <Button x:Name="MyButton" OtherCommand:MouseOut.Command="{Binding MouseOutCommand}" />
    </Grid>
</UserControl>

..and Codebehind

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace MyProject.View.NavigationWidgetModule
{
    public partial class NavigationWidgetControl : UserControl
    {
        public NavigationWidgetControl(NavigationWidgetViewModel viewModel)
        {
            this.DataContext = viewModel;
            InitializeComponent();
        }
    }
}

..and ViewModel

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
using Microsoft.Practices.Composite.Presentation.Commands;
using System.Threading;
using System.Windows.Threading;

namespace MyProject.View.NavigationWidgetModule
{
    public class NavigationWidgetViewModel : INotifyPropertyChanged
    {
        public DelegateCommand<object> MouseOutCommand { get; set; }


        public NavigationWidgetViewModel()
        {

            this.MouseOutCommand = new DelegateCommand<object>(OnMouseOut);
}

        public void OnMouseOut(object o)
        {

        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler  PropertyChanged;

        public void OnPropertyChanged(String s)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(s));
            }
        }
        #endregion

    }
}

<object><object></object></object>
Can you see anything wrong with that?

Aug 19, 2009 at 7:04 PM

Hi,

I have created a small sample application that shows this scenario working, so you can compare the differences with your solution. You can download it from here. When you "leave" the button with the mouse the command shows a message box.

I hope this is useful.

Damian Schenkelman
http://blogs.southworks.net/dschenkelman

Aug 19, 2009 at 7:30 PM

Hi Damian,

Thank you so much for that! It helped me to solve my problem. I didnt use the quickstart project; I built mine up by myself. And what I omitted was a reference to the Infrastructure assembly in the Shell project. After I noticed you put your behavior class in the module, I tried that and it worked. That was the "Ah ha" moment. Thanks again!

 

Sep 9, 2009 at 4:58 PM

Hello,

i'm sorry but i cannot seem to find CommandBehaviorBase<T> on the CAL Composite.Presentation assembly. 

Which release are you using ?

Francisco

Sep 27, 2010 at 6:27 PM

Hi, You can use following URL great work by Brian :)

http://geekswithblogs.net/HouseOfBilz/archive/2009/08/21/adventures-in-mvvm-ndash-generalized-command-behavior-attachments.aspx

 

 

 

 

@frantic: using Microsoft.Practices.Composite.Presentation.Commands; for CommanBehaviorBase<T>

 

 

 

 

Regards.

Sep 27, 2010 at 9:39 PM

Hi,

Thanks for sharing this with the rest of the community. Please, take into account that the product team is working on the Prism v4 version, which is focused on MVVM, so you will find in the last drops updated information on this topic.

Additionally, there is a new section in the Prism v4 CHM, which provides guidance about CommandBehaviorBase<T>. For more information you could take a look a the following documentation section:

  • Silverlight 3 Command Support (new)

Fernando Antivero 
http://blogs.southworks.net/fantivero

 

Aug 23, 2014 at 2:00 AM
Edited Aug 23, 2014 at 2:01 AM
@frantic0: import the following namespace in your class:
using Microsoft.Practices.Prism.Interactivity;