2

I am working with an app in Xamarin, but I want to make sure I am doing everything correctly as I go along. I have a Dialpad control that I display in MainPage through <ctrl:Dialpad x:Name="dialpad" HeightRequest="600"/>. MainPage has a BindingContext of MainPageViewModel and Dialpad has one of DialpadViewModel. I have an event in DialpadViewModel that gets called whenever a number key is pressed on the dialpad. For simplicity's sake, these buttons are meant to update a label on MainPage. In order to do this, I have this code in the code-behind file of MainPage.xaml.cs.

public MainPage()
    {
        InitializeComponent();

        //add an event handler for a button pressed
        InitializeEvents();

    }

    void InitializeEvents ()
    {
        //whenever the keypad is pressed
        ((DialpadViewModel)dialpad.BindingContext).OnKeyPressed += ((MainPageViewModel)BindingContext).DialpadButtonPressed;
        ((DialpadViewModel)dialpad.BindingContext).OnDial += ((MainPageViewModel)BindingContext).DialAddress;
        ((DialpadViewModel)dialpad.BindingContext).OnBackspace += ((MainPageViewModel)BindingContext).Backspace;
    }

I know, that according to the MVVM format, there is supposed to be NO code in the code-behind file. However, I have no idea how to approach this otherwise. Any advice?

mm8
  • 163,881
  • 10
  • 57
  • 88
Scornz
  • 380
  • 3
  • 14
  • Check out this question, and if it doesn't answer it edit your post to reflect that. https://stackoverflow.com/questions/1048517/wpf-calling-commands-via-events – Phoenix Stoneham Apr 03 '20 at 08:26

2 Answers2

3

Use an EventToCommandBehavior.

Overview

The EventToCommandBehavior class is a reusable Xamarin.Forms custom behavior that executes a command in response to any event firing. By default, the event arguments for the event will be passed to the command, and can be optionally converted by an IValueConverter implementation.

The following behavior properties must be set to use the behavior:

  • EventName – the name of the event the behavior listens to.
  • Command – the ICommand to be executed. The behavior expects to find the ICommand instance on the BindingContext of the attached control, which may be inherited from a parent element.

The following optional behavior properties can also be set:

  • CommandParameter – an object that will be passed to the command.
  • Converter – an IValueConverter implementation that will change the format of the event argument data as it's passed between source and target by the binding engine.

Note: The EventToCommandBehavior is a custom class that can be located in the EventToCommand Behavior sample, and is not part of Xamarin.Forms.

Creating the Behavior

The EventToCommandBehavior class derives from the BehaviorBase<T> class, which in turn derives from the Behavior class. The purpose of the BehaviorBase<T> class is to provide a base class for any Xamarin.Forms behaviors that require the BindingContext of the behavior to be set to the attached control. This ensures that the behavior can bind to and execute the ICommand specified by the Command property when the behavior is consumed.

The BehaviorBase<T> class provides an overridable OnAttachedTo method that sets the BindingContext of the behavior and an overridable OnDetachingFrom method that cleans up the BindingContext. In addition, the class stores a reference to the attached control in the AssociatedObject property.

Implementing Bindable Properties

The EventToCommandBehavior class defines four BindableProperty instances that execute a user defined command when an event fires. These properties are shown in the following code example:

C#:

public class EventToCommandBehavior : BehaviorBase<View> {  
    public static readonly BindableProperty EventNameProperty =
      BindableProperty.Create ("EventName", typeof(string), typeof(EventToCommandBehavior), null, propertyChanged: OnEventNameChanged);
    public static readonly BindableProperty CommandProperty =
      BindableProperty.Create ("Command", typeof(ICommand), typeof(EventToCommandBehavior), null);
    public static readonly BindableProperty CommandParameterProperty =
      BindableProperty.Create ("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null);
    public static readonly BindableProperty InputConverterProperty =
      BindableProperty.Create ("Converter", typeof(IValueConverter), typeof(EventToCommandBehavior), null);

    public string EventName { ... }
    public ICommand Command { ... }  
    public object CommandParameter { ... }
    public IValueConverter Converter { ...  }  
    ...  
}

When the EventToCommandBehavior class is consumed, the Command property should be data bound to an ICommand to be executed in response to the event firing that's defined in the EventName property. The behavior will expect to find the ICommand on the BindingContext of the attached control.

By default, the event arguments for the event will be passed to the command. This data can be optionally converted as it's passed between source and target by the binding engine, by specifying an IValueConverter implementation as the Converter property value. Alternatively, a parameter can be passed to the command by specifying the CommandParameter property value.

Implementing the Overrides

The EventToCommandBehavior class overrides the OnAttachedTo and OnDetachingFrom methods of the BehaviorBase<T> class, as shown in the following code example:

C#:

public class EventToCommandBehavior : BehaviorBase<View> {
  ...  
  protected override void OnAttachedTo (View bindable) {
    base.OnAttachedTo (bindable);
    RegisterEvent (EventName);
  }

  protected override void OnDetachingFrom (View bindable) {
    DeregisterEvent (EventName);
    base.OnDetachingFrom (bindable);
  }
... 
}

The OnAttachedTo method performs setup by calling the RegisterEvent method, passing in the value of the EventName property as a parameter. The OnDetachingFrom method performs cleanup by calling the DeregisterEvent method, passing in the value of the EventName property as a parameter.

Implementing the Behavior Functionality

The purpose of the behavior is to execute the command defined by the Command property in response to the event firing that's defined by the EventName property. }The core behavior functionality is shown in the following code example:

C#:

public class EventToCommandBehavior : BehaviorBase<View> {
...  
void RegisterEvent (string name) {
    if (string.IsNullOrWhiteSpace (name)) {
      return;
    }

    EventInfo eventInfo = AssociatedObject.GetType ().GetRuntimeEvent (name);
    if (eventInfo == null) {
      throw new ArgumentException (string.Format ("EventToCommandBehavior: Can't register the '{0}' event.", EventName));
    }
    MethodInfo methodInfo = typeof(EventToCommandBehavior).GetTypeInfo ().GetDeclaredMethod ("OnEvent");
    eventHandler = methodInfo.CreateDelegate (eventInfo.EventHandlerType, this);
    eventInfo.AddEventHandler (AssociatedObject, eventHandler);
  }

  void OnEvent (object sender, object eventArgs) {
    if (Command == null) {
      return;
    }

    object resolvedParameter;
    if (CommandParameter != null) {
      resolvedParameter = CommandParameter;
    } else if (Converter != null) {
      resolvedParameter = Converter.Convert (eventArgs, typeof(object), null, null);
    } else {
      resolvedParameter = eventArgs;
    }        

    if (Command.CanExecute (resolvedParameter)) {
      Command.Execute (resolvedParameter);
    }
  }
...
}

The RegisterEvent method is executed in response to the EventToCommandBehavior being attached to a control, and it receives the value of the EventName property as a parameter. The method then attempts to locate the event defined in the EventName property, on the attached control. Provided that the event can be located, the OnEvent method is registered to be the handler method for the event.

The OnEvent method is executed in response to the event firing that's defined in the EventName property. Provided that the Command property references a valid ICommand, the method attempts to retrieve a parameter to pass to the ICommand as follows:

  • If the CommandParameter property defines a parameter, it is retrieved.
  • Otherwise, if the Converter property defines an IValueConverter implementation, the converter is executed and converts the event argument data as it's passed between source and target by the binding engine.
  • Otherwise, the event arguments are assumed to be the parameter.

The data bound ICommand is then executed, passing in the parameter to the command, provided that the CanExecute method returns true.

Although not shown here, the EventToCommandBehavior also includes a DeregisterEvent method that's executed by the OnDetachingFrom method. The DeregisterEvent method is used to locate and deregister the event defined in the EventName property, to cleanup any potential memory leaks.

Consuming the Behavior

The EventToCommandBehavior class can be attached to the Behaviors collection of a control, as demonstrated in the following XAML code example:

XAML:

<ListView ItemsSource="{Binding People}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextCell Text="{Binding Name}" />
        </DataTemplate>
    </ListView.ItemTemplate>
    <ListView.Behaviors>
        <local:EventToCommandBehavior EventName="ItemSelected" Command="Binding OutputAgeCommand}" Converter="{StaticResource SelectedItemConverter}" />
    </ListView.Behaviors>
</ListView>
<Label Text="{Binding SelectedItemText}" />

The equivalent C# code is shown in the following code example:

C#:

var listView = new ListView();
listView.SetBinding(ItemsView<Cell>.ItemsSourceProperty, "People");
listView.ItemTemplate = new DataTemplate(() =>
{
    var textCell = new TextCell();
    textCell.SetBinding(TextCell.TextProperty, "Name");
    return textCell;
});
listView.Behaviors.Add(new EventToCommandBehavior {
    EventName = "ItemSelected",
    Command = ((HomePageViewModel)BindingContext).OutputAgeCommand,
    Converter = new SelectedItemEventArgsToSelectedItemConverter()
});

var selectedItemLabel = new Label();
selectedItemLabel.SetBinding(Label.TextProperty, "SelectedItemText");

The Command property of the behavior is data bound to the OutputAgeCommand property of the associated ViewModel, while the Converter property is set to the SelectedItemConverter instance, which returns the SelectedItem of the ListView from the SelectedItemChangedEventArgs.

At runtime, the behavior will respond to interaction with the control. When an item is selected in the ListView, the ItemSelected event will fire, which will execute the OutputAgeCommand in the ViewModel. In turn this updates the ViewModel SelectedItemText property that the Label binds to, as shown in the following screenshots:

Sample application with EventToCommandBehavior

The advantage of using this behavior to execute a command when an event fires, is that commands can be associated with controls that weren't designed to interact with commands. In addition, this removes boilerplate event handling code from code-behind files.

Source: Reusable EventToCommandBehavior

karel
  • 5,489
  • 46
  • 45
  • 50
2

This is not MVVM.

You basically have two options here.

  1. Expose an instance of a DialpadViewModel from the MainPageViewModel class. The latter can then subscribe to events of the former without any code-behind involved.

The Dialpad element in the MainPage binds to the DialpadViewModel property of the MainPageViewModel:

<ctrl:Dialpad x:Name="dialpad" BindingContext="{Binding DialpadViewModel}" HeightRequest="600"/>

Make sure that you don't explicitly set the BindingContext property of dialpad anywhere else.

  1. Use an event aggregator or a messenger to communicate between the DialpadViewModel and the MainPageViewModel in a loosely coupled way. This blog post explains the concept.

In short, there is no direct reference from one view model to another. Instead, they communicate with each other via a common object of some kind.

mm8
  • 163,881
  • 10
  • 57
  • 88