Search code examples
xamlmvvmwinui-3winui

Get Element Root's ViewModel Context in WINUI3


I have a page which contains a ListView x:bound to an object in my ViewModel. This object contains a list of objects (timestamps) that contains a list of Subjects that contains a list of another objects. I'm presenting the data in 2 list views, one inside another.

<ListView
  x:Name="primaryList" // for exemplification purposes
  ItemsSource="{x:Bind ViewModel.VideoProject.TimeStamps, Mode=OneWay}"
  ItemClick='{x:Bind ViewModel.ListViewTimeStamps_ItemClick, Mode=OneWay}'>

The ListView contains a DataTemplate for another ListView

<ListView.ItemTemplate>
  <DataTemplate>
  <StackPanel Spacing="5">
  <TextBlock Text="{Binding Id}"
  FontSize="15"
  HorizontalAlignment="Left"
  FontWeight="Bold" />
  
  <ListView ItemsSource="{Binding Subjects}"
  x:Name="secondaryList"
  SelectionMode="Multiple">
  <ListView.ItemTemplate>
....

And the second ListView is followed by another same structure.

My goal is to bind the second ListView ItemClickEvent to ListViewTimeStamps_ItemClick method inside my ViewModel, because I need the data contained in the object that secondaryListView holds (Subject). I could try to set the Data Template Context to the ViewModel but it would break the Subject bind.

I found a ton of questions about this topic but differently from WPF there's not AncestorType to catch the up tree reference.

Obs: My project is based on the Template Model which creates the XAML .cs with the ViewModel as a Property. I also haven't set the DataContext on the XAML page because I can x:bind normally my view model to the page elements without explicit set.

Is there a way to accomplish without using Attached Properties? Thank you.


Solution

  • Since there is no support for setting the AncestorType property of a RelativeSource in WinUI, there is no way to accomplish this in pure XAML without writing some code.

    You could implement an attached bevaiour as suggested and exemplified here:

    public static class AncestorSource
    {
        public static readonly DependencyProperty AncestorTypeProperty =
            DependencyProperty.RegisterAttached(
                "AncestorType",
                typeof(Type),
                typeof(AncestorSource),
                new PropertyMetadata(default(Type), OnAncestorTypeChanged)
        );
    
        public static void SetAncestorType(FrameworkElement element, Type value) =>
            element.SetValue(AncestorTypeProperty, value);
    
        public static Type GetAncestorType(FrameworkElement element) =>
            (Type)element.GetValue(AncestorTypeProperty);
    
        private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement target = (FrameworkElement)d;
            if (target.IsLoaded)
                SetDataContext(target);
            else
                target.Loaded += OnTargetLoaded;
        }
    
        private static void OnTargetLoaded(object sender, RoutedEventArgs e)
        {
            FrameworkElement target = (FrameworkElement)sender;
            target.Loaded -= OnTargetLoaded;
            SetDataContext(target);
        }
    
        private static void SetDataContext(FrameworkElement target)
        {
            Type ancestorType = GetAncestorType(target);
            if (ancestorType != null)
                target.DataContext = FindParent(target, ancestorType);
        }
    
        private static object FindParent(DependencyObject dependencyObject, Type ancestorType)
        {
            DependencyObject parent = VisualTreeHelper.GetParent(dependencyObject);
            if (parent == null)
                return null;
    
            if (ancestorType.IsAssignableFrom(parent.GetType()))
                return parent;
    
            return FindParent(parent, ancestorType);
        }
    }
    

    So no replacement so far for AncestorType?

    No. Not in XAML.