Using x:DataType with local class in Datatemplate throw System.InvalidCastException

Pierre MRQ 136 Reputation points
2020-11-15T15:21:49.68+00:00

Hello,

I have hierarchical data displayed in nested DataGrids. In the second datagrid, I want to access a property from the root of my object.

I have tried binding a DataTemplate through x:bind using the local namespace of my main page, but im getting an 'System.InvalidCastException' in the Debug console, when the page is loaded... (I guess it's because of the CellTemplate but im not sure)

How can I retrieve this value ? I've already tried the CollectionViewSource approach, but I haven't been able to get anything and this becomes problematic when I want to link these collections to ItemSources

Here is the code, I tried to leave what is necessary to make it clearer, I hope it's enough to understand my issue:

XAML
<Page.Resources>
<DataTemplate x:Key="myCellTemplate" x:DataType="local:CaseDetailPage">
            <TextBlock Text="{x:Bind ViewModel.SingleCase.Name}"/> <!-- Just for testing if the binding works-->
</DataTemplate>
 </Page.Resources>

<controls:DataGrid 
                            x:Name="mainGrid"
                            ItemsSource="{x:Bind ViewModel.CaseTasks}">
                    <controls:DataGrid.Columns>
                        <controls:DataGridTextColumn Header="Tasks" Binding="{Binding Name}" FontWeight="SemiBold" />
                    </controls:DataGrid.Columns>
                    <controls:DataGrid.RowDetailsTemplate>
                        <DataTemplate>
                            <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" >
                                <Grid>
                                    <controls:DataGrid
                                        x:Name="rowDatagrid"
                                        ItemsSource="{Binding CaseTasksDetails}">
                                        <controls:DataGrid.Columns>
                                            <controls:DataGridTemplateColumn Width="120" Header="Test" CellTemplate="{StaticResource myCellTemplate}"/>
                                        </controls:DataGrid.Columns>
                                    </controls:DataGrid>
                                </Grid>
                            </StackPanel>
                        </DataTemplate>
                    </controls:DataGrid.RowDetailsTemplate>
                </controls:DataGrid>




Page class:
public sealed partial class CaseDetailPage : Page
    {
        public CaseDetailViewModel ViewModel { get; } = new CaseDetailViewModel();


        public AffaireDetailPage()
        {
            this.InitializeComponent();
        }

protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);
            if (e.Parameter is long caseID)
            {
                //loading stuff
            }
}
}



Page ViewModel:
 public class AffaireDetailViewModel : Observable
    {
        private Case _case;

        public Case SingleCase
        {
            get { return _case; }
            set { Set(ref _case, value); }
        }

        public async Task InitializeAsync(long caseID)
        {
            var data = await SampleCaseDataService.GetContentGridDataAsync();
            SingleCase = data.First(i => i.caseID == caseID);
        }




Model classes:
public class Case
    {
        public string Name { get; set; }
        public ICollection<CaseTask> CaseTasks { get; set; }
    }


public class CaseTask
    {
        public string Name { get; set; }
        public ICollection<CaseTaskDetail> CaseTasksDetails {get; set;}
    }

public class CaseTaskDetail
    {
        public string Name { get; set; }
    }

Thanks in advance!

Universal Windows Platform (UWP)
0 comments No comments
{count} votes

Accepted answer
  1. Yan Gu - MSFT 2,676 Reputation points
    2020-11-16T03:15:52.323+00:00

    Hello,

    Welcome to Microsoft Q&A.

    I have used the code provided by you and supplemented part of the code for testing. You could check the following code to see if it can solve your exception:

    XAML

    <Page.Resources>  
        <DataTemplate x:Key="myCellTemplate" x:DataType="local:Case">  
            <TextBlock Text="{Binding Name}"/>  
            <!-- Just for testing if the binding works-->  
        </DataTemplate>  
    </Page.Resources>  
    <Grid>  
        <controls:DataGrid x:Name="mainGrid"  
                            ItemsSource="{x:Bind ViewModel.SingleCase.CaseTasks}">  
            <controls:DataGrid.Columns>  
                <controls:DataGridTextColumn Header="Tasks" Binding="{Binding Name}" FontWeight="SemiBold" />  
            </controls:DataGrid.Columns>  
            <controls:DataGrid.RowDetailsTemplate>  
                <DataTemplate>  
                    <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" >  
                        <Grid>  
                            <controls:DataGrid  
                                        x:Name="rowDatagrid"  
                                        ItemsSource="{Binding CaseTasksDetails}">  
                                <controls:DataGrid.Columns>  
                                    <controls:DataGridTemplateColumn Width="120" Header="Test" CellTemplate="{StaticResource myCellTemplate}"/>  
                                </controls:DataGrid.Columns>  
                            </controls:DataGrid>  
                        </Grid>  
                    </StackPanel>  
                </DataTemplate>  
            </controls:DataGrid.RowDetailsTemplate>  
        </controls:DataGrid>  
    </Grid>  
    

    Page class

    public sealed partial class CaseDetailPage: Page  
    {  
        public AffaireDetailViewModel ViewModel { get; } = new AffaireDetailViewModel();  
        public CaseDetailPage ()  
        {  
            this.InitializeComponent();  
        }  
    
        protected override void OnNavigatedTo(NavigationEventArgs e)  
        {  
           base.OnNavigatedTo(e);  
    
            if(e.Parameter is long caseID)  
            {  
                //  
            }  
        }  
    }  
    

    Page ViewModel

    public class AffaireDetailViewModel   
    {  
        private Case _case;  
        public Case SingleCase  
        {  
            get { return _case; }  
            set {_case =value; }  
        }  
        public AffaireDetailViewModel()  
        {  
            _case = new Case();  
            _case.Name = "Level1";  
        }  
        //The InitializeAsync method is replaced with the supplemented code  
        //public async Task InitializeAsync(long caseID)  
       // {  
       //     var data = await SampleCaseDataService.GetContentGridDataAsync();  
       //     SingleCase = data.First(i => i.caseID == caseID);  
       // }  
    
    }  
    

    Model classed

    public class Case  
    {  
        public string Name { get; set; }  
        public ObservableCollection<CaseTask> CaseTasks { get; set; }  
    
        public Case()  
        {  
            CaseTasks = new ObservableCollection<CaseTask>();  
            CaseTasks.Add(new CaseTask() { Name = "Level2_1" });  
            CaseTasks.Add(new CaseTask() { Name = "Level2_2" });  
            CaseTasks.Add(new CaseTask() { Name = "Level2_3" });  
        }  
    }  
    public class CaseTask  
    {  
        public string Name { get; set; }  
        public ObservableCollection<CaseTaskDetail> CaseTasksDetails { get; set; }  
        public CaseTask()  
        {  
            CaseTasksDetails = new ObservableCollection<CaseTaskDetail>();  
            CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_1" });  
            CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_2" });  
            CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_3" });  
        }  
    }  
    public class CaseTaskDetail  
    {  
        public string Name { get; set; }  
    }  
    

    Update:

    You could save the value of TestValue which you want to access inside some level of the DataGrid as a static value of MainPage, and add DataGrid.LoadingRowDetails event handler to change the value of TestValue based on the selected row in the first level of DataGrid. And show the value by a data binding with a Converter, like this:

    MainPage.xaml.cs

    public static string testString = "";  
    ……  
    private void dataGrid_LoadingRowDetails(object sender, DataGridRowDetailsEventArgs e)  
    {  
        int index = dataGrid.SelectedIndex;  
        testString = (index+2).ToString()+"th row: "+ ViewModel.SingleCase.TestValue2;  
    }  
    

    Converter class

    public class DateFormatter : IValueConverter  
    {  
        // This converts the DateTime object to the string to display.  
        public object Convert(object value, Type targetType,  
            object parameter, string language)  
        {  
            return MainPage.testString;  
        }  
    
        // No need to implement converting back on a one-way binding   
        public object ConvertBack(object value, Type targetType,  
            object parameter, string language)  
        {  
            throw new NotImplementedException();  
        }  
    }  
    

    MainPage.xaml

    <Page.Resources>  
        <local:DateFormatter x:Key="MyConverter"/>  
        ……  
    </Page.Resources>  
    
    //Use the Converter in a column inside the second level of DataGrid  
    <controls:DataGridTextColumn  Width="120" Header="Test" Binding="{Binding Converter={StaticResource MyConverter}}"/>  
    

    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

1 additional answer

Sort by: Most helpful
  1. Pierre MRQ 136 Reputation points
    2020-11-17T11:07:44.07+00:00

    Hello,

    Thanks for your help @Yan Gu - MSFT
    I have implemented your solution, unfortunately it still doesn't work.
    This time the problem is different and the error is a problem I had encountered before.

    Let me re-clarify the problem, I want to access some values at the "root" of my main object (which is an object with a hierachical structure) inside some level of my datagrid that have another datacontext, at the moment it looks like this:

    40394-capturedzadaz.png

    As you can see on the screenshot with the textblocks above the grid I can access these properties outside, but in the grid I can't and I get a binding error.
    The first level of my grid is binded with value1, the second with value2.

    The errors for first level is:
    Error: BindingExpression path error: 'TestValue' property not found on 'ProjectSolution.Core.Models.CaseTask'. BindingExpression: Path='TestValue' DataItem='ProjectSolution.Core.Models.CaseTask'; target element is 'Windows.UI.Xaml.Controls.TextBlock' (Name='null'); target property is 'Text' (type 'String')

    and the other is basically the same with the child items:
    Error: BindingExpression path error: 'TestValue2' property not found on 'ProjectSolution.Core.Models.CaseTaskDetail'. BindingExpression: Path='TestValue2' DataItem='ProjectSolution.Core.Models.CaseTaskDetail'; target element is 'Windows.UI.Xaml.Controls.TextBlock' (Name='null'); target property is 'Text' (type 'String')

    I have updated the code for clarity.

       <DataTemplate x:Key="templateCell1" x:DataType="models:Case">  
                   <Grid x:Name="ItemCellGrid">  
                       <TextBlock Text="{Binding TestValue}"></TextBlock>  
                   </Grid>  
       </DataTemplate>  
         
       <DataTemplate x:Key="templateCell2" x:DataType="models:Case">  
                   <Grid x:Name="ItemCellGrid">  
                       <TextBlock Text="{Binding TestValue2}"></TextBlock>  
                   </Grid>  
       </DataTemplate>  
         
         
       <controls:DataGrid   
                  x:Name="dataGrid"  
                  ItemsSource="{x:Bind ViewModel.SingleCase.CaseTasks}">  
                  <controls:DataGrid.Columns>  
                            <controls:DataGridTextColumn Header="Tasks" Binding="{Binding Name}" FontWeight="SemiBold" />  
                  </controls:DataGrid.Columns>  
                   <controls:DataGrid.RowDetailsTemplate>  
                             <DataTemplate>  
                                   <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" >  
                                       <Grid>  
                                           <controls:DataGrid  
                                               x:Name="rowDatagrid"  
                                               ItemsSource="{Binding CaseTasksDetails}">  
                                               <controls:DataGrid.Columns>  
                                                   <controls:DataGridTextColumn Header="Sub-Tasks" Binding="{Binding Name}"  FontWeight="SemiBold" />  
                                                   <controls:DataGridTemplateColumn Header="Test" CellTemplate="{StaticResource templateCell2}" />  
                                              </controls:DataGrid.Columns>  
                                          </controls:DataGrid>  
                                     </Grid>  
                               </StackPanel>  
                         <DataTemplate>  
              </controls:DataGrid.RowDetailsTemplate>  
       </controls:DataGrid>  
    
    
    
       public class Case  
        {  
            public string Name { get; set; }  
            public ObservableCollection<CaseTask> CaseTasks { get; set; }  
            public string TestValue { get; set; }  
            public string TestValue2 { get; set; }  
              
            public Case()  
            {  
                CaseTasks = new ObservableCollection<CaseTask>();  
                CaseTasks.Add(new CaseTask() { Name = "Level2_1" });  
                CaseTasks.Add(new CaseTask() { Name = "Level2_2" });  
                CaseTasks.Add(new CaseTask() { Name = "Level2_3" });  
            }  
        }  
        public class CaseTask  
        {  
            public string Name { get; set; }  
            public ObservableCollection<CaseTaskDetail> CaseTasksDetails { get; set; }  
            public CaseTask()  
            {  
                CaseTasksDetails = new ObservableCollection<CaseTaskDetail>();  
                CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_1" });  
                CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_2" });  
                CaseTasksDetails.Add(new CaseTaskDetail() { Name = "Level3_3" });  
            }  
        }  
        public class CaseTaskDetail  
        {  
            public string Name { get; set; }  
        }  
    
    
    
    
       public class CaseDetailViewModel   
        {  
            private Case _case;  
            public Case SingleCase  
            {  
                get { return _case; }  
                set {_case =value; }  
            }  
            public CaseDetailViewModel()  
            {  
                _case = new Case();  
                _case.TestValue = "Value1";  
                _case.TestValue2 = "Value2";  
            }  
    

    Note: the first datagridTemplateColumn is bound with code.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.