Ribbon Command Binding With TypeSafe Commands.

Ribbon for WPF Forum

Posted 10 years ago by Brette Esterbrooks
Version: 9.2.0510
Avatar
Hello,

I am running into a situation I am not sure how to solve. Basically I have created a implementation of the ICommand interface that allows the parameter to be type save as such.

SimpleCommand<SaveCommandParameter> SaveCommand = new SaveCommand<SaveCommandParameter>(CanExecute, Execute);
In my ribbon I have the following.

 <ribbon:Button Name="saveButton"
                               Label="Save"
                               Command="{Binding Path=SaveCommand}"
                               CommandParameter="{Binding Path=SaveCommandParameter}"
                               ImageSourceSmall="{ImageResources:StringResource Save}"
                               ImageSourceLarge="{ImageResources:StringResource Save}"
                               ToolTip="Save (F9 or Ctrl+S)"></ribbon:Button>
However when it runs I get a cast exception. It appears the ribbon is not using the parameter I specify?

2009-12-30 15:51:43,102 [10] FATAL SFM.Net.Application.Shell.App [(null)] - Unhandled Exception Received: System.InvalidCastException: Unable to cast object of type 'ActiproSoftware.Windows.Controls.Ribbon.Input.CheckableCommandParameter' to type 'SFM.Net.Infrastructure.Commands.SaveCommandParameter'.
at Microsoft.Practices.Composite.Wpf.Commands.DelegateCommand`1.System.Windows.Input.ICommand.CanExecute(Object parameter)

Comments (20)

Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Brette,

Buttons will default to having a CheckableCommandParameter in their CommandParameter when a Command property is assigned. Your CanExecute handler may be getting called right before your SaveCommandParameter is bound to the control. So instead of doing a cast in your CanExecute, do a C# try cast with the 'as' keyword to your command type. If it's non-null then use it, otherwise don't handle the CanExecute since the command parameter hasn't been updated yet.


Actipro Software Support

Posted 10 years ago by Brette Esterbrooks
Avatar
Dose this mean that all commands bound to actipro buttons need to have parameter types of object?

Brette
Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
No not at all. I'm just saying that your CanExecute handler is firing before your customized command parameter has been set. My guess is that in your CanExecute handler you cast the command parameter to your custom parameter type but since it hasn't been set to that type yet on the first call, you can an invalid cast exception. I was saying to use a try-cast instead. That way you can ensure your custom command parameter type has been set before executing your code, since again on the first call it is likely passing a default CheckableCommandParameter instance.


Actipro Software Support

Posted 10 years ago by Brette Esterbrooks
Avatar
I understand what you are saying. However if my CanSaveExecute takes a type other then 'Object' I will still get a cast exception when it tries to call it right? The binding will have to attempt a cast from CheckableCommandParameter to SaveCommandParameter right?

 /// <summary>
        /// Determines whether the save command can execute.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns>
        ///     <c>true</c> if this instance [can save execute] the specified parameter; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool CanSaveExecute(SaveCommandParameter parameter)
        {
            return true;
        }

Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Oh I see now, it's in the Microsoft code where this is happening. Hmm... this was an update that we recently did to make it easier for people to have checkable command parameters in place but it would seem perhaps the update affects the Microsoft framework you are using adversely.

Can you email over a simple sample project that shows the issue so we can debug it and make sure? Please reference this post in your email. Our offices are closed today but we'll look at it Monday. If a change is needed, we can make the change for the upcoming maintenance release.


Actipro Software Support

Posted 10 years ago by Brette Esterbrooks
Avatar
Sure I can do that what email do you want me to use?
Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Just our support address or you can submit a ticket via our web site here:
http://support.actiprosoftware.com/Main/frmNewTicket.aspx

That should allow attachments too.


Actipro Software Support

Posted 10 years ago by JTango - Adelaide, Australia
Avatar
Any word on where this is at in terms of getting a fix? We are having the exact same problem when using the latest Actipro builds with the DelegateCommand classes in Microsoft's Composite WPF (PRISM) library.

Cheers

Justin
Posted 10 years ago by Brette Esterbrooks
Avatar
Funny you say that! We are using the Composite Application Library as well. Looks like we are having the same problem. They asked me to put together a sample application illustrating the error. I plan on doing this today.

Brette
Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Please do get a sample over to us right away and we'll push a release out ASAP to get this resolved. Thanks.


Actipro Software Support

Posted 10 years ago by Brette Esterbrooks
Avatar
I just submitted the ticket.

Number(308-12D4B5E8-696C)

let me know if you got it.
Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Got it, if we upload a test build in a bit for the next WPF Studio release, would you be able to test it real fast for us? The change worked with your sample ok. If so, perhaps we could get an official build out later today or tomorrow.


Actipro Software Support

Posted 10 years ago by Brette Esterbrooks
Avatar
Yeah I could test it in our current application.
Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
The build that was just released should fix this problem.


Actipro Software Support

Posted 9 years ago by Tom P.
Avatar
I'm also using Prism and a DelegateCommand<T> implementation, and even with 10.1.523.0, this still exhibits the problem.

I'd really love a way around it.

Thoughts?
Posted 9 years ago by Brette Esterbrooks
Avatar
Tom,

We have this working in our commands now. Perhaps you could explain the scenario you have with some code? And I could show you what we have working. Perhaps the difference in our approaches will expose the bug.


Brette
Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Tom,

Thanks for the sample. ribbon:Button will by default put a CheckableCommandParameter in its command parameter. You can override it by setting CommandParameter in XAML however the override may not occur before the first time CanExecute is called on your command.

The code in the sample you sent us breaks in the last line here:
bool ICommand.CanExecute(object parameter)
{
    // if T is of value type and the parameter is not
    // set yet, then return false if CanExecute delegate
    // exists, else return true
    if (parameter == null &&
        typeof(T).IsValueType)
    {
        return this.canExecuteMethod == null;
    }

    return this.CanExecute((T)parameter);
}
Since in this case T == Type and the parameter is still CheckableCommandParameter at that time so it's an invalid cast.

If T had a class restraint on it then you could just do this and all would be good:
parameter as T

If you would rather keep T generic, you'd have to do something like this in your method:
bool ICommand.CanExecute(object parameter)
{
    // If T is a value type...
    if (typeof(T).IsValueType) {
        // If there is no parameter, or parameter is not a value type, or parameter is not the same value type, quit
        if ((parameter == null) || (!parameter.GetType().IsValueType) || (parameter.GetType() != typeof(T)))
            return false;
    }
    else {
        // T is not a value type... ensure a cast will work
        if (!typeof(T).IsInstanceOfType(parameter))
            return false;
    }

    return this.CanExecute((T)parameter);
}
We default to CheckableCommandParameter to make it easier for customers to work with checkable buttons. Otherwise they'd have to add 3 lines of XAML for each checkable control in the XAML.


Actipro Software Support

Posted 9 years ago by Tom P.
Avatar
Hi Bill,

I tried your suggestion, keeping T generic. Didn't quite work as expected. If I debug, for sure, the first time into ICommand.CanExecute(), I see that parameter is null, and it works, but then it gets called AGAIN with a CheckableCommandParameter, and it always returns false. So, what I get is my commands are NEVER enabled.

So, I added another button in the ribbon group:
<ribbon:Button
   Label="ribbon:Button"
   Command="{Binding TestCommand}"
   CommandParameter="{x:Type ribbon:Button}"
   />
And I noticed that this new Button was enabled properly. Seems to me that there is a special case with CommandParameter="{x:Null}" that isn't working.

Thoughts?
Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Right now if the value being set is null, the command parameter gets coerced to the default command parameter (CheckableCommandParameter).

What you can do in this case is just add this to the front of your procedure too:
if (parameter is ActiproSoftware.Windows.Controls.Ribbon.Input.CheckableCommandParameter)
    return this.CanExecute(default(T));
An easier way around all this would be to put a 'class' type restriction on T. Then you could just do this in your CanExecute:
return this.CanExecute(parameter as T);


Actipro Software Support

Posted 9 years ago by Tom P.
Avatar
Thanks for the suggestions. Unfortunately, I can't restrict T to class, and I want to be agnostic about actipro in this implementation, but you gave me a good idea. I changed my implementation as follows.
        /// <summary>
        /// ICommand.CanExecute implemenation
        /// </summary>
        /// <param name="parameter">Object to test</param>
        /// <returns>Boolean can execute</returns>
        bool ICommand.CanExecute(object parameter)
        {
            // if T is of value type and the parameter is not
            // set yet, then return false if CanExecute delegate
            // exists, else return true
            if (parameter == null &&
                typeof(T).IsValueType)
            {
                return this.canExecuteMethod == null;
            }
            else if (!(parameter is T))
            {
                parameter = null;
            }

            return this.CanExecute((T)parameter);
        }
        /// <summary>
        /// ICommand.Execute implemenation
        /// </summary>
        /// <param name="parameter">Object to test</param>
        void ICommand.Execute(object parameter)
        {
            if (!(parameter is T))
            {
                parameter = null;
            }

            this.Execute((T)parameter);
        }
The gist of this is that if the parameter type is not T, I just use null in it's place, for both the CanExecute and Execute methods. My concern is that this may be hiding other bugs, like if the XAML binds to the wrong type it will be harder to debug it, but I'll keep an eye on it.

Hopefully this will help others using a DelegateCommand<T> implementation of ICommand; often those doing MVVM and/or Prism stuff.

Thanks a lot for your super prompt customer support, as always.
The latest build of this product (v2019.1 build 0683) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.