michaellperry / Assisticant

MIT License
36 stars 19 forks source link

MakeCommand Parameter #33

Open ebadola opened 4 years ago

ebadola commented 4 years ago

Hi I would like to know how I could pass a Parameter with MakeCommand?

michaellperry commented 4 years ago

Thanks. Unfortunately that's not something that I've implemented yet, as I haven't had the need.

I can see a number of reasons for passing a parameter. Some of them have alternate solutions. For example, if you want to bind the command to several buttons or menu items, and have each one configured with a different CommandParameter, then you can instead use Linq to create an IEnumerable of child view models, each with its own command. Put those menu items or buttons in an ItemsControl.

If instead you want to data bind CommandParameter to a view model property, you can just have the command access that same property. The CanExecute method will detect changes in the bound property.

The correct implementation of the pattern depends upon how you are going to use it. If the parameter is always the same for a given instance, then it's just a pass through. But if the parameter can vary, then it would have to track changes on every possible value, just in case the lambda takes different paths based on the parameter. That's why I haven't yet put in the time to solve for it.

Let me know if one of the alternatives works. If not, perhaps we can come up with a good general-purpose solution that fits your needs.

ulath2000 commented 4 years ago

Hey guys,

we implemented (adjusted existing implementation) a while ago as it's commonly needed for us as well:


public static class MakeCommandWithParameter
    {
        private class Command : ICommand
        {
            // The condition under which it can execute, and the action to execute.
            private Func<bool> _canExecuteFunction;
            private Action<object> _execute;

            // A computed flag, true when the command can be executed.
            private bool _canExecute = false;
            private Computed _depCanExecute;

            public Command(Func<bool> canExecute, Action<object> execute)
            {
                _canExecuteFunction = canExecute;
                _execute = execute;

                // Create a computed sentry to control the "can execute" flag.
                _depCanExecute = new Computed(UpdateCanExecute);
                _depCanExecute.Invalidated += new Action(Invalidated);

                // It begins its life out-of-date, so prepare to update it.
                Invalidated();
            }

            public event EventHandler CanExecuteChanged;

            public bool CanExecute(object parameter)
            {
                // Just returning the flag. The flag gets set elsewhere.
                _depCanExecute.OnGet();
                return _canExecute;
            }

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

            private void UpdateCanExecute()
            {
                // Here is where the flag gets updated. The update function
                // is executed, and the result is stored in the "can execute"
                // flag. I become dependent upon anything that the update
                // function touches.
                _canExecute = _canExecuteFunction();
            }

            private void Invalidated()
            {
                // When the "can execute" flag is invalidated, we need to queue
                // up a call to update it. This will cause the UI thread to
                // call TriggerUpdate (below) when everything settles down.
                UpdateScheduler.ScheduleUpdate(UpdateNow);
            }

            private void UpdateNow()
            {
                // The "can execute" flag is now out-of-date. We need to update it.
                _depCanExecute.OnGet();

                // Now that it is up-to-date again, we need to notify anybody bound
                // to this command that the flag has changed.
                if (CanExecuteChanged != null)
                    CanExecuteChanged(this, new EventArgs());
            }
        }

        public class Condition
        {
            private Func<bool> _canExecute;

            public Condition(Func<bool> canExecute)
            {
                _canExecute = canExecute;
            }

            /// <summary>
            /// Specify an action to execute when the command is invoked. The action is a lambda
            /// taking no parameters and performing a statement. The syntax looks like: () => { DoSomething(); }
            /// </summary>
            /// <param name="execute">A lambda expression taking no parameters and performing a statement. The
            /// syntax looks like: () => { DoSomething(); }</param>
            /// <returns>A command that does that.</returns>
            public ICommand Do(Action<object> execute)
            {
                return new Command(_canExecute, execute);
            }
        }

        /// <summary>
        /// Specify a condition under which the command can be executed. Controls bound to the
        /// command will only be enabled when this condition is true. The condition is a lambda
        /// taking no parameters and returning a boolean. The syntax looks like: () => SelectedThing != null
        /// </summary>
        /// <param name="condition">A lambda expression taking no parameters and returing a boolean: () => SelectedThing != null</param>
        /// <returns>An object that you can add .Do to.</returns>
        public static Condition When(Func<bool> condition)
        {
            return new Condition(condition);
        }

        /// <summary>
        /// Specify an action to execute when the command is invoked. The action is a lambda
        /// taking no parameters and performing a statement. The syntax looks like: () => { DoSomething(); }
        /// </summary>
        /// <param name="execute">A lambda expression taking no parameters and performing a statement. The
        /// syntax looks like: () => { DoSomething(); }</param>
        /// <returns>A command that does that.</returns>
        public static ICommand Do(Action<object> execute)
        {
            return new Command(() => true, execute);
        }
    }

Use it like so - this way you can access it as type object as parameter.

 public ICommand TestCommand
        {
            get
            {
                return MakeCommandWithParameter
                   .Do((parameter) =>
                   {

                   });
            }
        }
michaellperry commented 4 years ago

In thinking about how to do this in a general, provable, unsurprising way, I've come up with the following plan.

The CanExecute function will memoize the execution by mapping the parameter to a Computed sentry. Calling with the same (Object.Equals) parameter will yield the same Computed, and will therefore use the cached result of the previous execution. Unless something has changed, in which case the Computed is out of date, and will recalculate appropriately.

By memoizing based on the parameter, we effectively turn it into a constant. This is important because, in general, the CanExecute function can take different paths for different parameters. A Computed records the execution path in the form of the Observables that it takes a dependency upon. Different parameters will potentially force dependencies upon different Observables.

The challenge, then, is that CanExecuteChanged notifies all bound controls. It cannot selectively notify. In general, several controls will bind to the same ICommand, and pass in different parameters from each other, and over time. When any one of the memoized Computeds goes out of date, then we need to raise CanExecuteChanged and dump the entire map.

With this plan, CanExecute will provide the correct dependency tracking behavior no matter how the ICommand is data bound, and how the CommandParameter is used. And that's the whole point: provably correct behavior without having to reason through it, and with no surprises.