James Armstrong
James Armstrong

Reputation: 600

Caliburn.Micro - Calling method guard when updating field of item in BindableCollection

I have a property BindableCollection<Cat> { get; private set; } with a number of Cat objects that is bound to an ItemsControl in my WPF XAML.

I then have the following code that is attached to a button inside this ItemsControl.

XAML snippet

<Button Content="Pat Cat" cal:Message.Attach="PatCat($dataContext)" />

In the ViewModel...

BindableCollection<Cat> { get; private set; }

public bool CanPatCat(Cat cat)
{
   return cat.Pattable && cat.Alive;
}

public void PatCat(Cat cat)
{
    // pat the cat
    cat.Alive = false;
}

Given this is a method guard and not a property, how do I notify the UI so that the CanPat method is evaluated for each Cat in the BindableCollection given the guard method will now return false once the Cat has been pat?

Upvotes: 1

Views: 404

Answers (2)

James Armstrong
James Armstrong

Reputation: 600

I ended up creating a separate ViewModel for each Cat, so a CatViewModel, with a corresponding CatView. I was then able to notify that the bindable collection had changed, and things worked as expected.

Note, I had incorrectly placed ViewModel logic into my Cat model. These were the properties that were changing on my model. I moved these into the CatViewModel.

Upvotes: 0

Il Vic
Il Vic

Reputation: 5666

While for properties a specific and sophisticated UI awareness mechanism exists (I am talking about Binding), for methods the situation is completely different. Using events in this context in my humble opinion is not suitable, so I thought a solution which is based on extending Caliburn Micro framework.

First of all we need to create a specific ActionMessage which is meant for re-executing the guard method after its invocation. I called it RefreshableActionMessage.

public class RefreshableActionMessage : ActionMessage
{
    private bool refreshAfterInvoke = true;

    public bool RefreshAfterInvoke
    {
        get { return refreshAfterInvoke; }
        set { refreshAfterInvoke = value; }
    }
}

Now we have to use it in our "guarded" button (yes, we need to use the verbose syntax):

<Button Content="Pat Cat">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <local:RefreshableActionMessage MethodName="PatCat">
                <cal:Parameter Value="{Binding}" />
            </local:RefreshableActionMessage>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

Just the code which is responsible to re-execute the guard method is missing, but we can add it in the Configure method in the Bootstrapper class:

protected override void Configure()
{
    ActionMessage.InvokeAction = delegate(ActionExecutionContext context)
    {
        RefreshableActionMessage refreshableActionMessage;

        object[] parameters = MessageBinder.DetermineParameters(context, context.Method.GetParameters());
        object obj = context.Method.Invoke(context.Target, parameters);
        Task task = obj as Task;
        if (task != null)
        {
            obj = task.AsResult();
        }
        IResult result = obj as IResult;
        if (result != null)
        {
            obj = new IResult[]
            {
                result
            };
        }
        IEnumerable<IResult> enumerable = obj as IEnumerable<IResult>;
        if (enumerable != null)
        {
            obj = enumerable.GetEnumerator();
        }
        IEnumerator<IResult> enumerator = obj as IEnumerator<IResult>;
        if (enumerator != null)
        {
            Coroutine.BeginExecute(enumerator, new CoroutineExecutionContext
            {
                Source = context.Source,
                View = context.View,
                Target = context.Target
            }, null);
        }

        refreshableActionMessage = context.Message as RefreshableActionMessage;
        if (refreshableActionMessage != null && refreshableActionMessage.RefreshAfterInvoke)
        {
            refreshableActionMessage.UpdateAvailability();
        }
    };
}

As you can see, if the custom ActionMessage requires a refresh, it is performed by calling the UpdateAvailability method.

I hope this solution can help you.

Upvotes: 1

Related Questions