Search code examples
datagridwinui-3windows-community-toolkitdatagridtemplatecolumnflyout

WinUI3 Datagrid TemplatedColumn with flyout button


I'm developing a WinUI3 application. I'd like to have a CommunityToolkit datagrid with a button column each row. When the button is clicked a flyout needs to show up letting user enters the data to populate that field.

For simplicity I put only two buttons in the example but the application will require more controls:

<controls:DataGridTemplateColumn Header="Value" IsReadOnly="True">
    <controls:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Grid>
                <Button Content="{Binding Value}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                    <Button.Flyout>
                        <Flyout>
                            <StackPanel>
                                <TextBlock Text="All items will be removed. Do you want to continue?" Margin="0,0,0,12" />
                                <Button Content="Yes, empty my cart" Click="Button_Click" />
                                <Button Content="No, cart is full" Click="FullButton_Click" />
                            </StackPanel>
                        </Flyout>
                    </Button.Flyout>
                </Button>
            </Grid>
        </DataTemplate>
    </controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>

Gif

As shown in the gif I've got clicking focus issues. First click works like a charm, further clicking seems to put cell in edit mode and flyout is not triggered. If I change the focus on other rows / cells results are different each time.

I think there's an editing / click pass through mechanism that I don't know how to avoid. Putting cell in "IsReadOnly" mode doesn't change the behaviour.

If I try to use a MenuFlyout or ContextFlyout on the cell it works great, but I need to show more controls and not a simple menu to be triggered on right click.


Solution

  • The DataGridComboBoxColumn used to have the same issue and you can see here how it was fixed.

    You can create a custom DataGrid and Column and apply a similar fix like this:

    UPDATE

    I updated my answer in way that you can cancel the edit mode explictly when the Flyout is closed.

    public class DataGridButtonWithFlyoutColumn : DataGridTemplateColumn
    {
        public event EventHandler<object>? FlyoutClosed;
    
        protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
        {
            if (editingElement is Button button)
            {
                button.Flyout.ShowAt(button);
                button.Flyout.Closed += (s, e) =>
                {
                    this.FlyoutClosed?.Invoke(this, e);
                };
            }
    
            return base.PrepareCellForEdit(editingElement, editingEventArgs);
        }
    }
    
    public class DataGridEx : DataGrid
    {
        public DataGridEx()
        {
            this.BeginningEdit += DataGridEx_BeginningEdit;
        }
    
        private void DataGridEx_BeginningEdit(object? sender, DataGridBeginningEditEventArgs e)
        {
            if (e.Column is DataGridButtonWithFlyoutColumn column)
            {
                column.FlyoutClosed += DataGridButtonWithFlyoutColumn_FlyoutClosed;
            }
        }
    
        private void DataGridButtonWithFlyoutColumn_FlyoutClosed(object? sender, object e)
        {
            _ = CancelEdit();
    
            if (sender is DataGridButtonWithFlyoutColumn column)
            {
                column.FlyoutClosed -= DataGridButtonWithFlyoutColumn_FlyoutClosed;
            }
        }
    }
    

    and use it like this:

    <local:DataGridEx
        x:Name="DataGridControl"
        ItemsSource="{x:Bind Items}">
        <toolkit:DataGrid.Columns>
            <toolkit:DataGridTextColumn
                Binding="{Binding Name}"
                Header="Name" />
            <toolkit:DataGridComboBoxColumn
                Binding="{Binding Type}"
                Header="Type"
                ItemsSource="{x:Bind Types, Mode=OneWay}" />
            <local:DataGridButtonWithFlyoutColumn Header="Value">
                <local:DataGridButtonWithFlyoutColumn.CellTemplate>
                    <DataTemplate>
                        <Grid Background="LightGreen">
                            <TextBlock
                                HorizontalAlignment="Stretch"
                                VerticalAlignment="Center"
                                HorizontalTextAlignment="Center"
                                Text="{Binding Value}" />
                        </Grid>
    
                    </DataTemplate>
                </local:DataGridButtonWithFlyoutColumn.CellTemplate>
                <toolkit:DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <Button
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            Background="HotPink"
                            Content="{Binding Value}"
                            FontWeight="ExtraBold">
                            <Button.Flyout>
                                <Flyout>
                                    <StackPanel>
                                        <TextBlock
                                            Margin="0,0,0,12"
                                            Text="All items will be removed. Do you want to continue?" />
                                        <Button
                                            Click="Button_Click"
                                            Content="Yes, empty my cart" />
                                        <Button
                                            Click="Button_Click_1"
                                            Content="No, cart is full" />
                                    </StackPanel>
                                </Flyout>
                            </Button.Flyout>
                        </Button>
                    </DataTemplate>
                </toolkit:DataGridTemplateColumn.CellEditingTemplate>
            </local:DataGridButtonWithFlyoutColumn>
        </toolkit:DataGrid.Columns>
    </local:DataGridEx>