Search code examples
c#event-handlingoverridingmaui

Single event handler in c# code for all custom buttons / MAUI .NET8


Simple ContentPage with a lot of buttons on it. I want a single event handler (OnButtonPress) to decode which button was pressed and take a variety of actions. Instead of tediously writing Clicked="OnButtonPress" a hundred times in the XAML, I thought (as I had defined a custom Button2:Button class anyway, for different reasons) to define this event handler as standard for the Button2 class.

Is there a way to do this? I've been searching and AI-ing for days, and can't seem to figure this one out, so would appreciate a pointer.

I've tried:

  • <Style />, but events are not bindable.
  • adding public EventHandler Clicked = "OnButtonPress" in the Button2 class, but it cannot implicitly convert type String to System.EventHandler.
  • putting this.Clicked += new EventHandler(MainPage.OnButtonPress); in the Button2 class, but it complains about how OnButtonPress doesn't exist in MainPage, when it clearly does.
  • moving OnButtonPress into the Button2 class, but then it complains about "the type or namespace OnButtonPress could not be found" (it's like... right there, dude).
  • this.Clicked = OnButtonPress();
  • this.Clicked += OnButtonPress(object sender, EventArgs e);

The general structure of the code is simple:

namespace Adam
{
  public partial class MainPage : ContentPage
  {
    public void OnButtonPress(object sender, EventArgs e)
    {
      // decoding stuff goes here
    }

    public class Button2 : Button
    {
      // lotsa properties and stuff added here

      public Button2()
      {
        // I didn't put any code in here
      }
    }
  }
}

and the XAML (I omitted all the grids and other stuff):

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Adam"
             x:Class="Adam.MainPage" 
             BackgroundColor="Black">
  <local:Button2 Text = "Test" />
>


Solution

  • So after more decades of research, I gave up trying to override the method, and instead turned to Commanding. Which I didn't want to do, originally, because of all the extra code I had to write.

    First off, I created my custom button class outside my MainPage class:

    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }
    }
    
    public class Button2 : Button
    {
        // stick variables in here
    }
    

    Then, I created a AdamViewModel class:

    public class AdamViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public ICommand ButtonCommand { private set; get; }
    
        public AdamViewModel()
        {
            ButtonCommand = new Command<string>(
            execute: (string arg) =>
            {
                // Button logic goes here
                RefreshCanExecutes();
            },
            canExecute: (string arg) =>
            {
                return true;
            });
        }
    
        void RefreshCanExecutes()
        {
            (ButtonCommand as Command).ChangeCanExecute();
        }
    

    Next up, I went to MainPage.xaml and added this section:

    <ContentPage.BindingContext>
        <local:AdamViewModel />
    </ContentPage.BindingContext>
    

    Then, in my custom button, I added CommandParameter="A0" and similar such code to the rest of my buttons.

    Finally, in Styles.xaml, I added code to style the button, and also the command:

        <Style TargetType="{x:Type local:Button2}">
            <Setter Property="TextColor" Value="White" />
            <Setter Property="BorderColor" Value="DimGrey" />
            <Setter Property="BorderWidth" Value="1" />
            <Setter Property="Margin" Value="3,0,3,0" />
            <Setter Property="Padding" Value="0" />
            <Setter Property="BackgroundColor" Value="Black" />
            <Setter Property="MinimumHeightRequest" Value="1" />
            <Setter Property="CornerRadius" Value="4" />
            <Setter Property="Command" Value="{Binding ButtonCommand}" />
        </Style>
    

    Note that last Setter, that binds the button to my single button processor/decoder logic.

    Which (sigh, exhausted breaths) finally got to the functionality I was trying to achieve - one single function to rule them all, and not writing so explicitly in the XAML a hundred times.

    I guess that it's not necessary to do a view-model class, in retrospect, but at least things are working now, and the view-model is kind of useful anyway for other stuff.