uwpflyout

UWP: find parent from FlyoutMenuItem click handler


Using UWP, I have a ListView where each item contains, among other things, a Button. Tapping the button opens a MenuFlyout. Tapping one of the flyout options triggers a Click event.

In the event handler, how do I find the parent Button or, alternatively, the parent ListView item ?

Here's an extract of my XAML. Specifically, I want to find the "Note" element while in the the OnItemEdit handler.

<Page.Resources>
  <ResourceDictionary>

    <DataTemplate x:Key="NoteItemTemplate">
      <Grid>
        <Grid.ColumnDefinitions> ... </Grid.ColumnDefinitions>
        <TextBox Name="Note" />
        <Button>
          <Image Source="..." />
          <Button.Flyout>
            <MenuFlyout>
              <MenuFlyoutItem Text="Edit" Click="OnItemEdit" />
              <MenuFlyoutItem Text="Delete" Click="OnItemDelete" />
            </MenuFlyout>
          </Button.Flyout>
        </Button>

      </Grid>
    </DataTemplate>
    ...
    <local:DetailItemSelector x:Key="DetailItemSelector"
      NoteItemTemplate="{StaticResource NoteItemTemplate}"
      ...
    />

  </ResourceDictionary>
</Page.Resources>

<ListView
  x:Name = "DetailList"
  ItemsSource = "{x:Bind DetailListItems}"
  ItemTemplateSelector = "{StaticResource DetailItemSelector}">
</ListView>

The handler is defined as:

async void OnItemEdit(object sender, RoutedEventArgs e)
{
  ...
}

EDIT - work around

If there's no better way, here's a work around that is not too bad. The trick is to use FlyoutBase.AttachedFlyout instead of Button.Flyout.

    <DataTemplate x:Key="NoteItemTemplate">
      <Grid>
        <Grid.ColumnDefinitions> ... </Grid.ColumnDefinitions>

        <TextBox ... Name="Note" />

        <Button ... Click=="onMoreClicked">
          <Image Source="..." />
          FlyoutBase.AttachedFlyout
            <MenuFlyout>
              <MenuFlyoutItem Text="Edit" Click="OnItemEdit" />
              <MenuFlyoutItem ... />
            </MenuFlyout>
          </FlyoutBase.AttachedFlyout>
        </Button>

      </Grid>
    </DataTemplate>

And, code behind

FrameworkElement lastItemTapped = null;

public void onMoreClicked (object sender, RoutedEventArgs e)
{
  Button button = sender as Button;

  // find parent list item containing button
  DependencyObject element = button;
  while (element != null)
  {
    if (element is FrameworkElement && (element as FrameworkElement).Name.Equals("Item"))
      break;
    element = VisualTreeHelper.GetParent(element);
  }
  if (element != null)
    lastItemTapped = element as FrameworkElement;

  // show flyout menu
  FlyoutBase.ShowAttachedFlyout(button);
}

public void OnItemEdit(object sender, RoutedEventArgs e)
{
  if (lastItemTapped == null)
    return;
  ...
}

Solution

  • Specifically, I want to find the "Note" element while in the the OnItemEdit handler.

    Assuming the DataTemplate is applied to a "Note", that should be as easy as casting the DataContext of the sender argument to a Note (or whatever your type is called):

    private void OnItemEdit(object sender, RoutedEventArgs e)
    {
        MenuFlyoutItem menuFlyoutItem = (MenuFlyoutItem)sender;
        var note = menuFlyoutItem.DataContext as YourNoteClass;
        ...
    }
    

    If you need a reference to the "Node" TextBox element, you can find it in the visual tree:

    private void OnItemEdit(object sender, RoutedEventArgs e)
    {
        MenuFlyoutItem menuFlyoutItem = (MenuFlyoutItem)sender;
        DependencyObject container = DetailList.ContainerFromItem(menuFlyoutItem.DataContext);
        TextBox note = FindVisualChild<TextBox>(container);
        ...
    }
    
    private static T FindVisualChild<T>(DependencyObject visual) where T : DependencyObject
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(visual, i);
            if (child != null)
            {
                T correctlyTyped = child as T;
                if (correctlyTyped != null)
                    return correctlyTyped;
    
                T descendent = FindVisualChild<T>(child);
                if (descendent != null)
                    return descendent;
            }
        }
        return null;
    }