J.D.
J.D.

Reputation: 1023

ImageButton.Click event works with 1 Label rendered on top of it but not 2 Labels

This appears to be an issue when running in Windows (haven't tested any other platform yet):

I have a single row & single column Grid with an ImageButton inside of it.

I then added a Label in the Grid so that it renders on top of the ImageButton. So far so good, the Click event works on the ImageButton.

I add one more Label to the same Grid after that, so it renders on top of the other Label, and on top of the ImageButton, and the Click event stops working. What gives?


The purpose of this UI is for a Scrabble game I'm making. This particular element is the tile which is an ImageButton with the appropriate background color, the first Label for the letter, and the second Label for the value of that letter. Example:

Scrabble Tile


Code:

// BoardCell.cs Class for the Scrabble Letter Tile
public class BoardCell : StackLayout
{
    public ImageButton TileImageButton { get; private set; }
    public Label TileLetterLabel { get; private set; }
    public Label TileValueLabel { get; private set; }

    public BoardCell(Enums.BoardCellTypes boardCellType)
    {
        // Setup the TileImageButton
        TileImageButton = new ImageButton();
        TileImageButton.WidthRequest = 80;
        TileImageButton.HeightRequest = 80;
        TileImageButton.BorderColor = Colors.DarkGray;
        TileImageButton.BorderWidth = 2;

        // Setup the Tile Grid
        var tileGrid = new Grid();
        tileGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
        tileGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });

        // Add the Tile Image Button
        tileGrid.Children.Add(TileImageButton);
        Grid.SetRow(TileImageButton, 0);
        Grid.SetColumn(TileImageButton, 0);

        // Setup and add the Tile Letter Label
        TileLetterLabel = new Label();
        TileLetterLabel.TextColor = Colors.Black;
        TileLetterLabel.FontSize = 48;
        TileLetterLabel.FontAttributes = FontAttributes.Bold;
        TileLetterLabel.HorizontalTextAlignment = TextAlignment.Center;
        TileLetterLabel.VerticalTextAlignment = TextAlignment.Center;
        TileLetterLabel.IsVisible = false; // Important this is initially false, otherwise it blocks clicks on the TileImageButton behind it

        tileGrid.Children.Add(TileLetterLabel);
        Grid.SetRow(TileLetterLabel, 0);
        Grid.SetColumn(TileLetterLabel, 0);

        // Setup and add the Tile Value Label
        TileValueLabel = new Label();
        TileValueLabel.TextColor = Colors.Black;
        TileValueLabel.FontSize = 12;
        TileValueLabel.FontAttributes = FontAttributes.Bold;
        TileValueLabel.HorizontalTextAlignment = TextAlignment.Center;
        TileValueLabel.VerticalTextAlignment = TextAlignment.End;

        tileGrid.Children.Add(TileValueLabel);
        Grid.SetRow(TileValueLabel, 0);
        Grid.SetColumn(TileValueLabel, 0);

        // Add the tileGrid to the StackLayout of this control
        this.Children.Add(tileGrid);

        if (boardCellType == Enums.BoardCellTypes.TileLetter)
        {
            SetTileValueLabel();
            TileLetterLabel.IsVisible = true;
        }
        else
        {
            TileLetterLabel.IsVisible = false;
        }
    }

    public void SetTileValueLabel()
    {
        this.TileValueLabel.Text = this.TileValue.ToString();
    }
}

// MainPage.xaml.cs
public partial class MainPage : ContentPage
{
    BoardCell SelectedTile { get; set; }

    public MainPage()
    {
        BoardTileTapGestureRecognizer = new TapGestureRecognizer();
        BoardTileTapGestureRecognizer.Tapped += BoardTileTapGestureRecognizer_Tapped;

        InitializeBoard();

        MyTileTapGestureRecognizer = new TapGestureRecognizer();
        MyTileTapGestureRecognizer.Tapped += MyTileTapGestureRecognizer_Tapped;
    }

    private void BoardTileTapGestureRecognizer_Tapped(object sender, EventArgs e)
    {
      // Does a bunch of stuff, but this event doesn't fire with this bug
    }

    private void MyTileTapGestureRecognizer_Tapped(object sender, EventArgs e)
    {
        // This one does fire
        SelectedTile = GetBoardCellParent(sender); // Sets the SelectedTile to clicked on BoardCell object
    }
}

private void InitializeBoard()
{
    for (int row = 0; row <= 14; row++)
    {
        // Setup new row and column for gameboard (only need to do this 15 times for each for a 15x15 grid)
        var tileRowDefinition = new RowDefinition() { Height = 80 };
        var tileColumnDefinition = new ColumnDefinition() { Width = 80 };

        grid_GameBoard.RowDefinitions.Add(tileRowDefinition);
        grid_GameBoard.ColumnDefinitions.Add(tileColumnDefinition);

        for (int column = 0; column <= 14; column++)
        {
            // Create a new board cell and set it's clicked event
            BoardCell boardCell = GetNewBoardCell(row, column);
            boardCell.TileImageButton.Clicked += OnBoardCellClicked;
            boardCell.TileLetterLabel.GestureRecognizers.Add(BoardTileTapGestureRecognizer);

            // Add the new board cell to the grid                
            grid_GameBoard.Children.Add(boardCell);
            Grid.SetRow(boardCell, row);
            Grid.SetColumn(boardCell, column);
        }
    }

private void OnBoardCellClicked(object sender, EventArgs e)
{
  // Does a few things but never gets hit with this bug
}

// MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ScrabbleMaui2.MainPage">

    <ScrollView>
        <!--<Grid RowSpacing="25" RowDefinitions="Auto,Auto,Auto,Auto,*"
              Padding="{OnPlatform iOS='30,60,30,30', Default='30'}">-->

        <Grid x:Name="grid_Game" RowDefinitions="50,*,100" ColumnDefinitions="*" MaximumHeightRequest="1500">
            <Grid x:Name="grid_TitleBar" Grid.Row="0" ColumnDefinitions="*,*,*">
                <Grid x:Name="grid_Score" ColumnDefinitions="*,*">
                    <Label Grid.Column="0" Text="My Score: " />
                    <Label x:Name="lbl_MyScore" Grid.Column="1" Text="0" />
                </Grid>

                <Button x:Name="btn_SubmitPlay" Grid.Column="2" Text="Play Word" WidthRequest="100" Clicked="btn_SubmitPlay_Clicked" />
            </Grid>

            <Grid x:Name="grid_GameBoard" Grid.Row="1" Margin="10" HorizontalOptions="Center" BackgroundColor="Green" />

            <Grid x:Name="grid_MyTiles" Grid.Row="2" RowDefinitions="Auto" ColumnDefinitions="*,*,*,*,*,*,*" MaximumWidthRequest="620"
                  BackgroundColor="Blue" />
        </Grid>
    </ScrollView>

</ContentPage>


Per ToolmakerSteve's suggestion, I re-arrange the controls I'm using to build my Tile object. I now place a transparent button last, so it's rendered as the top most element. This is the new code in the BoardCell.cs constructor:

public BoardCell(Enums.BoardCellTypes boardCellType, bool isCenterCell)
{
    this.IsLocked = false;
    this.BoardCellType = boardCellType;
    this.IsCenterCell = isCenterCell;

    var tileGrid = new Grid();
    tileGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
    tileGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });

    TileBackground = new Image();
    TileLetterLabel = GetLetterLabel();
    TileValueLabel = GetValueLabel();

    SetTileBackground();
    SetTileValueLabel();

    TileButton = new Button();
    TileButton.WidthRequest = 80;
    TileButton.HeightRequest = 80;
    TileButton.IsVisible = false;

    tileGrid.Children.Add(TileBackground);
    tileGrid.Children.Add(TileLetterLabel);
    tileGrid.Children.Add(TileValueLabel);
    tileGrid.Children.Add(TileButton);

    Grid.SetRow(TileBackground, 0);
    Grid.SetRow(TileLetterLabel, 0);
    Grid.SetRow(TileValueLabel, 0);
    Grid.SetRow(TileButton, 0);

    Grid.SetColumn(TileBackground, 0);
    Grid.SetColumn(TileLetterLabel, 0);
    Grid.SetColumn(TileValueLabel, 0);
    Grid.SetColumn(TileButton, 0);

    this.Children.Add(tileGrid);
}

I'm now using a separate Image element as the background, and a regular Button element to be clicked on. Unfortunately I still exhibit the same behavior, oddly. The button's Click event doesn't fire as long as the TileValueLabel is added as a child to the Grid. If I comment out the lines that add it to the Grid - tileGrid.Children.Add(TileValueLabel); then Click event fires.

Upvotes: 0

Views: 86

Answers (1)

ToolmakerSteve
ToolmakerSteve

Reputation: 21233

Sounds like there is a bug with passing input through multiple elements.

Try wrapping the two labels in a second grid, with InputTransparent on that inner grid:

<Grid ..>
  <ImageButton ../>
  <Grid InputTransparent="True">
    <Label ../>
    <Label ../>
  </Grid>
</Grid>

Upvotes: 0

Related Questions