3

Issue: I'm trying to create a menu on a DataGrid when right-clicking on a row.

Goal: Is it possible to create a menu on a DataGrid when right-clicking on a row; I'm able to create one on a cell?

<controls:DataGridTemplateColumn Header="OrderId">
    <controls:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ContextFlyout>
                    <MenuFlyout>
                        <MenuFlyoutItem Text="Copy" Icon="Copy" Click="MenuFlyoutItem_Copy" />
                        <MenuFlyoutSeparator />
                        <MenuFlyoutItem Text="Delete" Icon="Delete" Click="MenuFlyoutItem_Delete" />
                    </MenuFlyout>
                </Grid.ContextFlyout>
                <TextBlock Text="{Binding OrderId}" />
            </Grid>
        </DataTemplate>
    </controls:DataGridTemplateColumn.CellTemplate>
</controls:DataGridTemplateColumn>

private void MenuFlyoutItem_Copy(object sender, RoutedEventArgs e)
{
    ObservableCollection<SampleOrder> dataGrid = DataGrid.ItemsSource as ObservableCollection<SampleOrder>;

    MenuFlyoutItem mfi = sender as MenuFlyoutItem;
    SampleOrder seleted = mfi.DataContext as SampleOrder;

    var copiedItem = (SampleOrder)seleted.Clone();

    dataGrid.Add(copiedItem);
}

private void MenuFlyoutItem_Delete(object sender, RoutedEventArgs e)
{
    ObservableCollection<SampleOrder> dataGrid = DataGrid.ItemsSource as ObservableCollection<SampleOrder>;

    MenuFlyoutItem mfi = sender as MenuFlyoutItem;
    SampleOrder seleted = mfi.DataContext as SampleOrder;

    dataGrid.Remove(seleted);
}
James Ellis
  • 31
  • 1
  • 5

2 Answers2

10

Unless I'm missing something, this is much easier than Nico's answer makes out. All you need to do is set the RowStyle property like this:

<controls:DataGrid.RowStyle>
    <Style TargetType="controls:DataGridRow">
        <Setter Property="controls:DataGridRow.ContextFlyout">
            <Setter.Value>
                <MenuFlyout>
                    <MenuFlyoutItem x:Name="MyMenuItem"
                                    Click="MyMenuItem_Click"
                                    Text="Do Things" />
                </MenuFlyout>
            </Setter.Value>
        </Setter>
    </Style>
</controls:DataGrid.RowStyle>

And then in your handler:

private void MyMenuItem_Click(object sender, RoutedEventArgs e)
{
    var item = (sender as FrameworkElement).DataContext as MyModel;
    // Do things with your item.
}
shawnseanshaun
  • 1,071
  • 1
  • 13
  • 25
  • Instead of getting the model for the right clicked row, I seem to be getting the ViewModel for the whole page :/ Any idea why that might be? (I'm using WinUI3 instead of UWP.) – Felix Dec 20 '21 at 21:44
  • My bad, I was setting the ContextFlyout on the DataGrid instead of in the RowStyle. – Felix Dec 20 '21 at 21:52
  • This solution works greatly for me. However, I could I select a DataGrid row when I right-click on it, in order to toggle menu entries? – ZipGenius Jan 15 '22 at 11:24
  • @ZipGenius Can't try it myself right now, but from memory, you probably want something like `(sender as MenuFlyoutItem).ContextFlyout.Target`. – shawnseanshaun Jan 21 '22 at 18:59
0

If you want to add MenuFlyout to the row, you need to custom row style and add the ContextFlyout to the DataGridCellsPresenter.

<localprimitives:DataGridCellsPresenter x:Name="CellsPresenter" Grid.Column="1"
    localprimitives:DataGridFrozenGrid.IsFrozen="True" MinHeight="32"
    AutomationProperties.AccessibilityView="Raw">
        <localprimitives:DataGridCellsPresenter.ContextFlyout>
            <MenuFlyout>
                <MenuFlyoutItem Text="Copy" Icon="Copy" Click="MenuFlyoutItem_Copy" />
                <MenuFlyoutSeparator />
                <MenuFlyoutItem Text="Delete" Icon="Delete" Click="MenuFlyoutItem_Delete" />
            </MenuFlyout>
        </localprimitives:DataGridCellsPresenter.ContextFlyout>
 </localprimitives:DataGridCellsPresenter>

The following is a complete DataGridRow style that you could use directly

<Style TargetType="controls:DataGridRow">
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="controls:DataGridRow">
                <localprimitives:DataGridFrozenGrid x:Name="RowRoot">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <Rectangle
                        x:Name="BackgroundRectangle"
                        Grid.ColumnSpan="2"
                        Fill="{ThemeResource SystemControlTransparentBrush}"
                        />
                    <Rectangle
                        x:Name="InvalidVisualElement"
                        Grid.ColumnSpan="2"
                        Fill="{ThemeResource DataGridRowInvalidBrush}"
                        Opacity="0"
                        />

                    <localprimitives:DataGridRowHeader
                        x:Name="RowHeader"
                        Grid.RowSpan="3"
                        localprimitives:DataGridFrozenGrid.IsFrozen="True"
                        />
                    <localprimitives:DataGridCellsPresenter
                        x:Name="CellsPresenter"
                        Grid.Column="1"
                        MinHeight="32"
                        localprimitives:DataGridFrozenGrid.IsFrozen="True"
                        AutomationProperties.AccessibilityView="Raw"
                        >
                        <localprimitives:DataGridCellsPresenter.ContextFlyout>
                            <MenuFlyout>
                                <MenuFlyoutItem
                                    Click="MenuFlyoutItem_Copy"
                                    Icon="Copy"
                                    Text="Copy"
                                    />
                                <MenuFlyoutSeparator />
                                <MenuFlyoutItem
                                    Click="MenuFlyoutItem_Delete"
                                    Icon="Delete"
                                    Text="Delete"
                                    />
                            </MenuFlyout>
                        </localprimitives:DataGridCellsPresenter.ContextFlyout>
                    </localprimitives:DataGridCellsPresenter>
                    <localprimitives:DataGridDetailsPresenter
                        x:Name="DetailsPresenter"
                        Grid.Row="1"
                        Grid.Column="1"
                        AutomationProperties.AccessibilityView="Raw"
                        Background="{ThemeResource DataGridDetailsPresenterBackgroundBrush}"
                        />
                    <Rectangle
                        x:Name="BottomGridLine"
                        Grid.Row="2"
                        Grid.Column="1"
                        Height="1"
                        HorizontalAlignment="Stretch"
                        />
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="NormalAlternatingRow" />
                            <VisualState x:Name="PointerOver">
                                <Storyboard>
                                    <ColorAnimation
                                        Storyboard.TargetName="BackgroundRectangle"
                                        Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                        To="{ThemeResource SystemListLowColor}"
                                        Duration="0"
                                        />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="NormalSelected">
                                <Storyboard>
                                    <ColorAnimation
                                        Storyboard.TargetName="BackgroundRectangle"
                                        Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                        To="{ThemeResource DataGridRowSelectedBackgroundColor}"
                                        Duration="0"
                                        />
                                    <DoubleAnimation
                                        Storyboard.TargetName="BackgroundRectangle"
                                        Storyboard.TargetProperty="Opacity"
                                        To="{ThemeResource DataGridRowSelectedBackgroundOpacity}"
                                        Duration="0"
                                        />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="PointerOverSelected">
                                <Storyboard>
                                    <ColorAnimation
                                        Storyboard.TargetName="BackgroundRectangle"
                                        Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                        To="{ThemeResource DataGridRowSelectedHoveredBackgroundColor}"
                                        Duration="0"
                                        />
                                    <DoubleAnimation
                                        Storyboard.TargetName="BackgroundRectangle"
                                        Storyboard.TargetProperty="Opacity"
                                        To="{ThemeResource DataGridRowSelectedHoveredBackgroundOpacity}"
                                        Duration="0"
                                        />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="PointerOverUnfocusedSelected">
                                <Storyboard>
                                    <ColorAnimation
                                        Storyboard.TargetName="BackgroundRectangle"
                                        Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                        To="{ThemeResource DataGridRowSelectedHoveredUnfocusedBackgroundColor}"
                                        Duration="0"
                                        />
                                    <DoubleAnimation
                                        Storyboard.TargetName="BackgroundRectangle"
                                        Storyboard.TargetProperty="Opacity"
                                        To="{ThemeResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}"
                                        Duration="0"
                                        />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="UnfocusedSelected">
                                <Storyboard>
                                    <ColorAnimation
                                        Storyboard.TargetName="BackgroundRectangle"
                                        Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                        To="{ThemeResource DataGridRowSelectedUnfocusedBackgroundColor}"
                                        Duration="0"
                                        />
                                    <DoubleAnimation
                                        Storyboard.TargetName="BackgroundRectangle"
                                        Storyboard.TargetProperty="Opacity"
                                        To="{ThemeResource DataGridRowSelectedUnfocusedBackgroundOpacity}"
                                        Duration="0"
                                        />
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="ValidationStates">
                            <VisualState x:Name="Valid" />
                            <VisualState x:Name="Invalid">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames
                                        Storyboard.TargetName="BackgroundRectangle"
                                        Storyboard.TargetProperty="Visibility"
                                        Duration="0"
                                        >
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <DoubleAnimation
                                        Storyboard.TargetName="InvalidVisualElement"
                                        Storyboard.TargetProperty="Opacity"
                                        To="0.4"
                                        Duration="0"
                                        />
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </localprimitives:DataGridFrozenGrid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Please note the style contains a lot ThemeResource comes from windows-community-toolkit. Before using the above style, you need add MergedDictionaries to your app resource.

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGrid.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

Update 2

what is the proper way of getting the row index from which the menu was executed from

@JamesEllis, You could get row index in MenuFlyoutItem_Copy or MenuFlyoutItem_Delete event handler. Please refer the following code.

private void MenuFlyoutItem_Copy(object sender, RoutedEventArgs e)
{
    var menu = sender as MenuFlyoutItem;
    var item = menu.DataContext as Item;
    var items = dataGrid.ItemsSource as List<Item>;
    var index = items.IndexOf(item);
}
Nico Zhu
  • 32,367
  • 2
  • 15
  • 36
  • Ok got it to work, menu option works find..but now when you click on a cell to edit its data the UWP app crashes any idea what maybe the cause? Thank you. – James Ellis Feb 20 '19 at 16:50
  • Have you added `ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls.DataGrid/DataGrid/DataGrid.xaml` to app resource? – Nico Zhu Feb 21 '19 at 02:22
  • Yes added it to App.xaml – James Ellis Feb 21 '19 at 13:20
  • Does it under the `` node? – Nico Zhu Feb 22 '19 at 02:17
  • please review [Code Sample](https://github.com/Scnck/POC_Basket), not sure what is causing this issue. – James Ellis Feb 25 '19 at 17:52
  • I have checked your code, There are many differences about the `ThemeResource`. such `DataGridRowSelectedHoveredBackgroundColor` to `DataGridRowSelectedHoveredBackground`. Please use the above style derictly. – Nico Zhu Feb 26 '19 at 04:24
  • Thank you that work. One additional question when you use the new menu options what is the proper way of getting the row index from which the menu was executed from; I can't find any example related to UWP? – James Ellis Feb 26 '19 at 15:40
  • @JamesEllis, You could get row index in `MenuFlyoutItem_Copy` or `MenuFlyoutItem_Delete` event handler. Please check above update – Nico Zhu Mar 06 '19 at 03:16