Why won't my Command trigger when my button is clicked?

EasyGoingPatEasyGoingPat ✭✭✭GBMember ✭✭✭
edited August 2018 in Xamarin.Forms

Following MVVM principles, I am attempting to fire a command in my view-model when a button in the view is clicked.

I have a valid view and a valid view-model and the view-model is set as the DataContext for the view. To verify that I have a working binding to the view-model, I have created a Label, which I can successfully bind to a string property on the view-model. When I add the binding for the Button.Command, I get a completely useless error that says: 'Error while resolving expression: One or more errors occurred.'

Interestingly, I get this error if I define the type of the command as an ICommand. If I define it as Command, I don't get the error (but it still doesn't work).

One thing I wondered about is that I have defined my own ICommand interface to avoid pulling System.Windows.Input into my core logic module. And I have created my own concrete classes Command and Command<T> that implement the interface. Does this matter? It certainly didn't use to. I know this because I am currently recreating the whole project from another Xamarin.Forms application in which all of this commanding stuff was working just fine.

Anyone got any ideas? It would be nice even to get some communication from the binding engine. I love the idea of data binding but it seems to either work or not work and never seems to give useful information when it is not working.

  • Patrick

Best Answer

  • EasyGoingPatEasyGoingPat ✭✭✭ GB ✭✭✭
    edited August 2018 Accepted Answer

    I have now got to the bottom of this. And I am glad I didn't file a bug because the problem turned out to be something quite different.

    Something has changed in Xamarin because the code as it stood worked a year ago and now is broken. However, this change seems to be nothing directly to do with commands. It turned out the binding problem was caused by the way I was constructing the views and their associated view-logic instances. When InitializeComponent() was called, the BindingContext was not yet set. This stopped the commands binding. I'm not sure if the code contained in InitializeComponent() is supposed to run whenever the BindingContext is changed for the ContentPage but it seems that the answer to this question was once yes and now is no. I guess the real question is how my code was ever working in the first place, but there is no point in trying to figure this out now because as @ClintStLaurent pointed out above, a year is a lifetime for Xamarin.

    So, to be clear on the subject of the commands in case anyone stumbles across this thread, it binds exactly as expected to a System.Windows.Input.ICommand or a Xamarin.Forms.Command (which is what @seanyda said above).

    It also binds just fine to my own implementation of the ICommand interface, which I have gone back to using because it makes a minor bit of boilerplate code just that bit simpler.

    I'll mark this as the answer simply because it summarises all that has gone before.

    Thanks for the help. always good to bounce something off other progs.

    • Patrick

Answers

  • seanydaseanyda ✭✭✭✭✭ GBMember ✭✭✭✭✭

    When you say DataContext, do you mean BindingContext? If you have set that to the correct ViewModel you can bind like below:

    public ICommand PlayButtonCommand { get { return new Command(PlayAudio); } }
    
            async void PlayAudio()
            {
                // my play audio logic
            }
    

    You would bind "PlayButtonCommand" to the Command property of your button.

  • EasyGoingPatEasyGoingPat ✭✭✭ GBMember ✭✭✭

    @seanyda

    Hi. Sorry, yes, I do mean BindingContext (been doing Windows WPF/XAML in which they call it DataContext).

    Your example is precisely how I have it. Except that creating my command with the type ICommand, as you show, gives the error I mentioned. But note that the full type of my ICommand is not System.Windows.Input.ICommand. I have defined my own interface.

  • ClintStLaurentClintStLaurent ✭✭✭✭✭ USUniversity ✭✭✭✭✭

    @EasyGoingPat said:
    But note that the full type of my ICommand is not System.Windows.Input.ICommand. I have defined my own interface.

    Maybe just a little early in your Xamarin learning to be making your own ICommand interface?
    Maybe try making everything work out-of-the-box first. Then make new things only when you've outgrown the existing items.

    Try this same app-problem with standard-out-of-the-box binding and commands.

  • EasyGoingPatEasyGoingPat ✭✭✭ GBMember ✭✭✭

    @ClintStLaurent

    Thanks for the reply. I take your point but the ICommand I am using was working perfectly a year ago. So, if Xamarin has 'improved' things in the meantime, it is a breaking change and ought to be documented.

    I think the next painful step is probably to dig out the whole of the old solution, get it building and provisioned, and see if that still works. That will tell me if my code has been broken by a Xamarin update since I last worked on it or if I have made an error in my recreation of the project.

    I shall report back with my findings.

  • ClintStLaurentClintStLaurent ✭✭✭✭✭ USUniversity ✭✭✭✭✭

    @EasyGoingPat said:
    I take your point but the ICommand I am using was working perfectly a year ago.

    That's a lifetime ago in Xamarin terms. Massive volume of changes in the last year.

    Out of nothing more than personal curiosity... Why did you have to make your own ICommand interface? What was lacking?

    So, if Xamarin has 'improved' things in the meantime, it is a breaking change and ought to be documented.

    To be fair... How they document every possible repercussion of a change? I doubt they knew it was a breaking change when they made it or they would have just fixed it so it didn't break.
    To be direct... They can't manage to write enough good documentation now. Xamarin documentation is WAY lacking and way old. Funny enough, I even asked (okay, hinted) about updated documenation/books just this morning.
    Let alone add to that burden with 10,000 "what if" cases trying to guess what someone might have done.

  • EasyGoingPatEasyGoingPat ✭✭✭ GBMember ✭✭✭

    @ClintStLaurent

    I take your point about the difficulties of documenting rare use-cases. I don't see what I did as being particularly odd though. I can't find the website where I originally got the idea for this but the guy seemed to know his onions and, as I say, it was all working perfectly.

    It does seem I am right, that something has changed in Xamarin that has broken the whole architecture I had. Unless I use the following types System.Windows.Input.ICommand and Xamarin.Forms.Command, the binding architecture simply doesn't see my command in the view-model.

    I implemented my own ICommand and Command for architectural reasons. I took the decision that even Xamarin.Forms was itself a platform, since it was primarily concerned with UI and it should be possible to swap it out if necessary.

    So, taking the Android project as an example, I had this: MyProject.Android -> MyProject.Forms -> MyProject.Core. Each project knows nothing about any project to its left. MyProject.Core had one single dependency, the .NETStandard library. So even the Xamarin.Forms project had to inject its services into the core module using interfaces. It all seemed clean and easy to manage.

    It now appears to be entirely broken.

    I can think of two solutions:

    1. I move my view-logic into the MyProject.Forms layer. I'm not fond of this idea because I went to great pains to keep perhaps 70+% of the project's code dependent upon nothing but the .NET libraries and it seems a shame to give up on this now and tie it all into Xamarin.Forms. I mean, it is just conceivable that a different UI framework could be used in future. If I start directly using types from Xamarin.Forms and then needed to use a different UI, I'd be looking at a complete re-write. Okay, I guess I am being over-cautious but... well, I've been burnt by lack of caution too many times in the past.

    2. I create some kind of Command factory in the MyProject.Forms layer and inject this into the MyProject.Core layer. This should be possible because .NETStandard does at least provide the System.Windows.Input.ICommand interface. Maybe it is OCD but I will probably give this a go.

    I'll try a proper implementation of this to check that everything works as expected and then add a summary answer, which might help anyone else (with OCD) who stumbles across this problem.

    Thanks for the input, guys.

    • Patrick
  • EasyGoingPatEasyGoingPat ✭✭✭ GBMember ✭✭✭

    I have refactored my code so that the commands are created by a command factory.

    In MyProject.Core, I have my view-model:

    public class HomeViewLogic : INotifyPropertyChanged
    {
        public System.Windows.Input.ICommand MyButtonClickCommand = CommandFactory.CreateNewCommand( ( o ) => throw new Exception( "Got Command" ); );
    }
    

    In MyProject.Forms, I have the implementation of the command factory:

    public class CommandFactory : ICommandFactory
    {
        public ICommand CreateNewCommand( Action<object> executeAction )
        {
            return new Xamarin.Forms.Command( executeAction );
        }
    }
    

    In App.xaml.cs, my view is created something like this:

    MainPage = new HomeView();
    MainPage.BindingContext = new HomeViewLogic();
    

    And my XAML attempts to bind like this:

    <ContentPage>
        <Button x:Name="testButton" Command="{Binding MyButtonClickCommand}"/>
    </ContentPage>
    

    This gives no error at compilation or runtime but does not work. If I manually attach the command in the XAML code-behind like this...

    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class HomeView : ContentPage
    {
        public HomeView()
        {
            InitializeComponent();
        }
    
        protected override void OnAppearing()
        {
            testButton.Command = (HomeViewLogic)BindingContext.MyButtonClickCommand;
            base.OnAppearing();
        }
    }
    

    ...the command is triggered when the button is clicked. So it kind-of half-works. The binding engine cannot bind to the command but once the binding is supplied, it can trigger the action associated with the command.

    I am guessing the binding engine is having trouble 'seeing' into a different assembly. To me, this seems to be a big breaking change because - at the risk of repeating myself - this all used to work perfectly. My only solutions now appear to be to manually attach all commands or fundamentally change my once-working application architecture.

    I'm considering filing this as a bug because it surely should not be a requirement for binding that the object being bound to is in the same assembly as Xamarin.Forms? I'd really appreciate some input from a Xamarin guy or gal here? Is there something I can perhaps add to my XAML to get this working again? Was there a legitimate reason this was changed?

    • Patrick
  • EasyGoingPatEasyGoingPat ✭✭✭ GBMember ✭✭✭
    edited August 2018 Accepted Answer

    I have now got to the bottom of this. And I am glad I didn't file a bug because the problem turned out to be something quite different.

    Something has changed in Xamarin because the code as it stood worked a year ago and now is broken. However, this change seems to be nothing directly to do with commands. It turned out the binding problem was caused by the way I was constructing the views and their associated view-logic instances. When InitializeComponent() was called, the BindingContext was not yet set. This stopped the commands binding. I'm not sure if the code contained in InitializeComponent() is supposed to run whenever the BindingContext is changed for the ContentPage but it seems that the answer to this question was once yes and now is no. I guess the real question is how my code was ever working in the first place, but there is no point in trying to figure this out now because as @ClintStLaurent pointed out above, a year is a lifetime for Xamarin.

    So, to be clear on the subject of the commands in case anyone stumbles across this thread, it binds exactly as expected to a System.Windows.Input.ICommand or a Xamarin.Forms.Command (which is what @seanyda said above).

    It also binds just fine to my own implementation of the ICommand interface, which I have gone back to using because it makes a minor bit of boilerplate code just that bit simpler.

    I'll mark this as the answer simply because it summarises all that has gone before.

    Thanks for the help. always good to bounce something off other progs.

    • Patrick
Sign In or Register to comment.