Martin Crawley
Martin Crawley

Reputation: 487

Xamarin Custom View Button Binding

I have a custom view with 2 button in it - One on the left and one on the right.

I want to have a bindable property to dictate which function will be called by each button, but the function I want to call is in the original content page, not the custom control.

I'm aware that you can't have a void as the bindable property so I'm not sure how to achieve this.

Here's my custom control xaml:

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns="http://xamarin.com/schemas/2014/forms" 
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        x:Class="EngineerApp.HeaderBar"  HeightRequest="50">
    <BoxView x:Name="banner" BackgroundColor="#edeff2"
        RelativeLayout.WidthConstraint="{ ConstraintExpression 
            Type=RelativeToParent,
            Property=Width,
            Factor=1 }"
        RelativeLayout.HeightConstraint="{ ConstraintExpression 
        Type=RelativeToParent,
        Property=Height,
        Factor=1 }">
    </BoxView>
    <Button 
        Text="Left"
        x:Name="btnLeft"
        Style="{StaticResource headerBarButton}"
        RelativeLayout.WidthConstraint="{ ConstraintExpression 
            Type=RelativeToView,
            ElementName=banner,
            Property=Width,
            Factor=0.5 }"       
        RelativeLayout.HeightConstraint="{ ConstraintExpression 
            Type=RelativeToView,
            ElementName=banner,
            Property=Height,
            Factor=1 }"
        >
    </Button>
    <Button 
        Text="Right" 
        Style="{StaticResource headerBarButton}"
        RelativeLayout.XConstraint="{ ConstraintExpression 
            Type=RelativeToView,
            ElementName=banner,
            Property=Width, 
            Factor=0.5,
            Constant=1
        }"
        RelativeLayout.WidthConstraint="{ ConstraintExpression 
            Type=RelativeToView,
            ElementName=banner,
            Property=Width,
            Factor=0.5 }"       
        RelativeLayout.HeightConstraint="{ ConstraintExpression 
            Type=RelativeToView,
            ElementName=banner,
            Property=Height,
            Factor=1 }"
        >
    </Button>
</RelativeLayout>

And here's the backend code:

using System;
using System.Collections.Generic;
using System.Windows.Input;
using Xamarin.Forms;

namespace EngineerApp
{
    public partial class HeaderBar : RelativeLayout
    {

        public HeaderBar()
        {

            InitializeComponent();
        }

    }
}

And finally I'm including the custom new as normal with

<local:HeaderBar></local:HeaderBar>

How could I bind each of the 2 buttons click events to a method in my Content Page?

Upvotes: 3

Views: 2240

Answers (2)

Sharada
Sharada

Reputation: 13601

Solution-1

You can expose a couple of events in your custom control.

Steps:

  1. Create 2 events in your custom HeaderBar control.

    public event EventHandler RightButtonClickEvent;
    public event EventHandler LeftButtonClickEvent;
    
  2. Assign Clicked event-handlers in XAML, and invoke these events in them

    void RightButton_Clicked(object sender, EventArgs e)
    {
        RightButtonClickEvent?.Invoke(sender, e);
    }
    
    void LeftButton_Clicked(object sender, EventArgs e)
    {
        LeftButtonClickEvent?.Invoke(sender, e);
    }
    
  3. Bind custom events with event handlers in your ContentPage

    <local:HeaderBar
       RightButtonClickEvent="OnRightButtonClick"
       LeftButtonClickEvent="OnLeftButtonClick" />
    

Sample code - HeaderBar.xaml.cs

public partial class HeaderBar : RelativeLayout
{
    public HeaderBar()
    {
        InitializeComponent();
    }

    public event EventHandler RightButtonClickEvent;
    public event EventHandler LeftButtonClickEvent;

    void RightButton_Clicked(object sender, EventArgs e)
    {
        RightButtonClickEvent?.Invoke(sender, e);
    }

    void LeftButton_Clicked(object sender, EventArgs e)
    {
        LeftButtonClickEvent?.Invoke(sender, e);
    }
}

Updated sample code - HeaderBar.xaml

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="EngineerApp.HeaderBar"  HeightRequest="50">
<BoxView x:Name="banner" BackgroundColor="#edeff2"
    RelativeLayout.WidthConstraint="{ ConstraintExpression 
        Type=RelativeToParent,
        Property=Width,
        Factor=1 }"
    RelativeLayout.HeightConstraint="{ ConstraintExpression 
    Type=RelativeToParent,
    Property=Height,
    Factor=1 }">
</BoxView>
<Button 
    Text="Left"
    x:Name="btnLeft"
    Clicked="LeftButton_Clicked"
    Style="{StaticResource headerBarButton}"
    RelativeLayout.WidthConstraint="{ ConstraintExpression 
        Type=RelativeToView,
        ElementName=banner,
        Property=Width,
        Factor=0.5 }"       
    RelativeLayout.HeightConstraint="{ ConstraintExpression 
        Type=RelativeToView,
        ElementName=banner,
        Property=Height,
        Factor=1 }"
    >
</Button>
<Button 
    Text="Right" 
    x:Name="btnRight"
    Clicked="RightButton_Clicked"
    Style="{StaticResource headerBarButton}"
    RelativeLayout.XConstraint="{ ConstraintExpression 
        Type=RelativeToView,
        ElementName=banner,
        Property=Width, 
        Factor=0.5,
        Constant=1
    }"
    RelativeLayout.WidthConstraint="{ ConstraintExpression 
        Type=RelativeToView,
        ElementName=banner,
        Property=Width,
        Factor=0.5 }"       
    RelativeLayout.HeightConstraint="{ ConstraintExpression 
        Type=RelativeToView,
        ElementName=banner,
        Property=Height,
        Factor=1 }"
    >
</Button>
</RelativeLayout>

Sample code - MyContentPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
  xmlns:local="clr-namespace:EngineerApp" 
  x:Class="EngineerApp.MyContentPage">
  <local:HeaderBar
    RightButtonClickEvent="OnRightButtonClick"
    LeftButtonClickEvent="OnLeftButtonClick" />
</ContentPage>

Sample code - MyContentPage.xaml.cs

public partial class MyContentPage : ContentPage
{
    public MyContentPage()
    {
        InitializeComponent();
    }

    void OnRightButtonClick(object sender, EventArgs e)
    {
        //handle right-button click here
    }

    void OnLeftButtonClick(object sender, EventArgs e)
    {
        //handle left-button click here
    }
}

Solution-2 (recommended if you are using MVVM)

Expose two commands as bindable properties (which in turn will be bound to inner button controls). You can then bind these new commands to properties in your ContentPage's ViewModel

Sample code - Header.xaml.cs

public partial class HeaderBar : RelativeLayout
{
    public HeaderBar()
    {
        InitializeComponent();

        //create binding between parent control and child controls
        btnLeft.SetBinding(Button.CommandProperty, new Binding(nameof(LeftButtonCommand), source: this));
        btnRight.SetBinding(Button.CommandProperty, new Binding(nameof(RightButtonCommand), source: this));
    }

    public static readonly BindableProperty LeftButtonCommandProperty =
        BindableProperty.Create(
        "ILeftButtonCommand", typeof(ICommand), typeof(HeaderBar),
            defaultValue: null);

    public ICommand LeftButtonCommand
    {
        get { return (ICommand)GetValue(LeftButtonCommandProperty); }
        set { SetValue(LeftButtonCommandProperty, value); }
    }

    public static readonly BindableProperty RightButtonCommandProperty =
        BindableProperty.Create(
        "RightButtonCommand", typeof(ICommand), typeof(HeaderBar),
            defaultValue: null);

    public ICommand RightButtonCommand
    {
        get { return (ICommand)GetValue(RightButtonCommandProperty); }
        set { SetValue(RightButtonCommandProperty, value); }
    }
} 

Sample code - MyContentPage.xaml

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
  xmlns:local="clr-namespace:EngineerApp" 
  x:Class="EngineerApp.MyContentPage">
  <local:HeaderBar
      LeftButtonCommand="{Binding LeftClickCommand}"
      RightButtonCommand="{Binding RightClickCommand}" />
</ContentPage>

Sample code - MyContentPageViewModel.cs

public class MyContentPageViewModel : ViewModelBase //base implementation for INotifyPropertyChanged
{
    private ICommand _leftClickCommand; 
    public ICommand LeftClickCommand
    {
        get
        {
            return _leftClickCommand ??
                (_leftClickCommand = new Command((obj) =>
                {
                    //handle left button click here.
                }));
        }
    }

    private ICommand _rightClickCommand;
    public ICommand RightClickCommand
    {
        get
        {
            return _rightClickCommand ??
                (_rightClickCommand = new Command((obj) =>
                {
                    //handle right button click here.
                }));
        }   
    }
}

Upvotes: 5

hugo
hugo

Reputation: 1849

I had a similar issue. I created a CalculatorPage.xaml (my custom control) and a linked CalculatorViewModel.

For each button inside my custom control:

    <Button Grid.Row="0" Grid.Column="1" Font="Large" Command="{Binding Calculator.KeyPressCommand}" CommandParameter="Eight" Text="8" />

In the above example, "KeyPressCommand" exists in the CalculatorViewModel. But "Calculator" does not exist.

I created a new MainView (my main View) and the corresponding MainViewModel.

As you did, I included the custom control in my XAML like this:

<common:CalculatorView></common:CalculatorView>

And in the MainViewModel, I have a property

public CalculatorViewModel Calculator { get; private set; } = new CalculatorViewModel();

There is maybe a better solution where you define the BindingContext of the custom control but my solution is working fine

Upvotes: 0

Related Questions