Search code examples
c++-winrt

How to reference parent DataContext in user control [C++/winrt]


Edit: Solved via blank user control, not custom control (See below solution) I'm basically trying to modularize complex XAML stuff away from MainPage into a user control, hence I need to figure out how to inject the local DataContext within MainPage.Xaml into my user control.

Following the MSDN Doc's tutorial for custom user controls, I have a custom control in my MainPage.xaml file, called NutriDetailsControl.

// MainPage.xaml


...<ListBox x:Name="ItemList" ItemsSource="{x:Bind Basket}" RelativePanel.Below="AddDelCol"
      <ItemsControl.ItemTemplate>
         <DataTemplate x:DataType="local:BasketItem"> 
            <StackPanel GotFocus="ItemGotFocus" Orientation="Horizontal">
               <Button Content="Delete" Click="DeleteItemClickHandler"/>
               <TextBlock VerticalAlignment="Center" Text="{x:Bind Name, Mode=OneWay}"/>
HERE------->   <local:NutriDetailsControl Background="Red" MyItem="Hello, World!" MyBasketItem="x:Bind ???"/>
            ...
         ...
      ...
   ...
...</ListBox>

MyItem is a string, and I am able to access it within the custom user control, but I can't access MyBasketItem, which is a DependencyProperty of type BasketItem (ViewModel variable within the local DataContext). In Generic.Xaml, I've tried to access BasketItem's data via binding locally, and through the DependencyProperty I passed in (MyBasketItem),

<!-- \Themes\Generic.xaml -->
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:NutritionBasket">

 <Style TargetType="local:NutriDetailsControl" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:NutriDetailsControl">
                    <Grid Width="30" Height="30" Background="{TemplateBinding Background}">
(THIS WORKS)------>     <!--<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{TemplateBinding MyItem}"/>-->
                        <Button HorizontalAlignment="Right" VerticalAlignment="Center" Content=">">
                            <Button.Flyout>
                                <Flyout>
                                    <Flyout.FlyoutPresenterStyle>
                                        <Style TargetType="FlyoutPresenter">
                                            <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled"/>
                                            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
                                            <Setter Property="IsTabStop" Value="True"/>
                                            <Setter Property="TabNavigation" Value="Cycle"/>
                                        </Style>
                                    </Flyout.FlyoutPresenterStyle>
                                    <StackPanel>
    LOOK HERE ---------------->          <TextBlock Text="{x:Bind Name}"/>
     AND HERE ---------------->          <TextBlock Text="{TemplateBinding MyBasketItem.Name}"/>
                                    </StackPanel>
                                </Flyout>
                            </Button.Flyout>
                        </Button>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

For the first line, I am trying to access BasketItem->Name() via the local DataContext. I am getting an error "This Xaml file must have a code-behind to use {x:Bind}".

For the second line, when I am passing in BasketItem as a DependencyProperty within NutriDetailsControl.h, I am getting "MyBasketItem is not supported in a Windows Universal project"

I know that for Name(), I can simply pass it in as a DependencyProperty; my GOAL is to reference another variable, BasketItemNutri() which is a sub-ViewModel variable of BasketItem. So I need to figure out a way to either pass in BasketItem, or BasketItemNutri, from the MainPage into custom user control.

Any help would be appreciated. What should I do?


Solution

  • Solved

    I wasn't able to get the Nico's solution to work for me, since I still needed to somehow bind within NutriDetailsControl.xaml to MyBasketItem, and no permutation I tried seemed to work.

    After reading the Data Binding in Depth, I thought maybe I should forget about this whole custom control thing situation, and just go with a blank user control, called NutriControl.

    I created a DependencyProperty in NutriControl.idl (blank user control) by adding

    static Windows.UI.Xaml.DependencyProperty MyBasketItemProperty{ get; };
            BasketItem MyBasketItem;
    

    (Note that MyBasketItemProperty and MyBasketItem; must be the same). A DependencyProperty of a user control, "exposed" through the NutriControl.idl file, is basically like a box that you can put data into from other Xaml files (like MainPage.xaml). The data can be text, class objects (including your viewmodels), etc.

    // Mainpage.Xaml
    <local:NutriControl MyBasketItem="{x:Bind}"/>
    

    "x:Bind" binds to the local DataContext (DataType??), which was a BasketItem here, so I didn't need to specify anything else.

    // NutriControl.h
    ...other stuff...
    struct NutriControl : NutriControlT<NutriControl>
        {
            NutriControl();
    
            NutritionBasket::BasketItem MyBasketItem()
            {
                return winrt::unbox_value<NutritionBasket::MyBasketItem>(GetValue(m_myBasketItemProperty));
            }
    
            void MyBasketItem(NutritionBasket::MyBasketItem const& value)
            {
                SetValue(m_myBasketItemProperty, winrt::box_value(value));
            }
    
            static Windows::UI::Xaml::DependencyProperty MyBasketItemProperty() { return m_myBasketItemProperty; }
    
            static void OnMyBasketItemChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);
    
        private:
            static Windows::UI::Xaml::DependencyProperty m_myBasketItemProperty;
        };
    }
    ...other stuff...
    
    // NutriControl.c
        NutriControl::NutriControl() {
            InitializeComponent();
        }
    
        Windows::UI::Xaml::DependencyProperty NutriControl::m_myBasketItemProperty =
            Windows::UI::Xaml::DependencyProperty::Register(
                L"MyBasketItem",
                winrt::xaml_typename<NutritionBasket::BasketItem>(),
                winrt::xaml_typename<NutritionBasket::NutriControl>(),
                Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &NutriControl::OnMyBasketItemChanged } }
        );
    
        void NutriControl::OnMyBasketItemChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& /* e */)
        {
           // still unimplemented
        }
    
    // NutriControl.Xaml
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
            <Button HorizontalAlignment="Right" VerticalAlignment="Center" Content=">">
                <Button.Flyout>
                    <Flyout>
                        <Flyout.FlyoutPresenterStyle>
                            <Style TargetType="FlyoutPresenter">
                                <Setter Property="ScrollViewer.HorizontalScrollMode" Value="Disabled"/>
                                <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
                                <Setter Property="IsTabStop" Value="True"/>
                                <Setter Property="TabNavigation" Value="Cycle"/>
                            </Style>
                        </Flyout.FlyoutPresenterStyle>
                        <StackPanel>
     see here-------->      <TextBlock Text="{x:Bind MyBasketItem.Nutrition.Amount, Mode=OneWay}"></TextBlock>
     and here-------->      <ListBox ItemsSource="{x:Bind MyBasketItem.Nutrition.Elems}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate x:DataType="local:BasketItemNutriElem">
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock VerticalAlignment="Center" Text="{x:Bind Nutrient, Mode=OneWay}"/>
                                            <TextBlock VerticalAlignment="Center" Text="{x:Bind Amount, Mode=OneWay}"/>
                                        </StackPanel>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ListBox>
                        </StackPanel>
                    </Flyout>
                </Button.Flyout>
            </Button>
        </StackPanel>
    

    This worked. MyBasketItem (-> Nutrition) and (->Elems->BasketItemNutriElem) are both sub-viewmodels of type BasketItem, so I've successfully passed local instances of viewmodels from MainPage.Xaml to a user control.