xamluwpuwp-xamltemplate10

Property can't be found on ViewModel in UWP app


An Order form in UWP using Template 10 adds products to an order. The error is

Invalid binding path 'OrderViewModel.FindProduct_TextChanged' : Property 'OrderViewModel' can't be found on type 'ProductViewModel'

The relevant xaml snippet is

<Page.DataContext>
    <ViewModels:MainPageViewModel x:Name="OrderViewModel" />
</Page.DataContext>

<GridView  ItemsSource="{x:Bind OrderViewModel.Products, Mode=TwoWay}">
    <GridView.ItemTemplate>
        <DataTemplate x:DataType="ViewModels:ProductViewModel" >
            <AutoSuggestBox 
                Name="ProductAutoSuggestBox" 
                TextMemberPath="{x:Bind ItemCode, Mode=TwoWay}"
                TextChanged="{x:Bind OrderViewModel.FindProduct_TextChanged}">
            </AutoSuggestBox>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

The relevant snippet from the OrderViewModel and the ProductViewModel

namespace ViewModels
{    
    public class OrderViewModel : ViewModelBase
    {
        public ObservableCollection<Product> Products { get; set; } = new ObservableCollection<Product>();

        public void FindProduct_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
        { ... }
    }

    public class ProductViewModel : ViewModelBase
    {
        string _ItemCode = default(string);
        public string ItemCode { get { return _ItemCode; } set { Set(ref _ItemCode, value); } }

        public ProductViewModel()
        {
        }
    }
}

How to I correctly reference FindProduct_TextChanged on the OrderViewModel from the DataTemplate for the GridView which references ProductViewModel?


Solution

  • Voted up to @tao's comment. @Vague, I think you may misunderstand what x:DataType is used for. You can refer to the "DataTemplate and x:DataType" part of Data binding in depth:

    When using {x:Bind} in a data template, so that its bindings can be validated (and efficient code generated for them) at compile-time, the DataTemplate needs to declare the type of its data object using x:DataType.

    For your scenario, from your code public ObservableCollection<Product> Products { get; set; } = new ObservableCollection<Product>();, the type of your DataTemplate's data object should be your Product class, not your ProductViewModel, and in the meanwhile, your FindProduct_TextChanged event must be find in this Product class, that means your code of FindProduct_TextChanged should be placed in your Product data model.

    By the way, I think there is no need to use TwoWay binding for ItemsSource. For this scenario, the binding target is ItemsSource of GridView, the binding source is ObservableCollection<Product> Products, I understand you want to update GridView when your collection is updated, this is work is done with ObservableCollection. Besides, only the binding source here can be changed to notify the binding target, so OneWay binding is enough. But it's not a big problem with your code.

    So for your GridView, it should be something like this:

    <GridView  ItemsSource="{x:Bind OrderViewModel.Products, Mode=OneWay}">
        <GridView.ItemTemplate>
            <DataTemplate x:DataType="Models:Product" >
                <AutoSuggestBox 
                    Name="ProductAutoSuggestBox" 
                    TextMemberPath="{x:Bind ItemCode, Mode=TwoWay}"
                    TextChanged="{x:Bind FindProduct_TextChanged}">
                </AutoSuggestBox>
            </DataTemplate>
        </GridView.ItemTemplate>
    </GridView>