1

I have a DataGrid with 2 columns. Based on the first column which is bound to ParameterDataType I want to load the appropriate template in the second column.

Problem with this code is before the DataGrid has been loaded, the template selector is executing as a result the item is null. Is there a way to execute the Template Selector after the ControlTemplate's DataContext is set. Please help.

Here is my xaml:

<uwpControls:DataGrid Grid.Row="4"
                              ItemsSource="{x:Bind ViewModel.ServiceMethodsData,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                               AutoGenerateColumns="False">

<uwpControls:DataGrid.Resources>
                <DataTemplate x:Key="StringTemplate">
                    <TextBox Width="150" Height="30" VerticalAlignment="Center"></TextBox>
                </DataTemplate>
                <DataTemplate x:Key="IntegerTemplate">
                    <controls:TextBoxNumeric Width="150" Height="30" VerticalAlignment="Center"></controls:TextBoxNumeric>
                </DataTemplate>
                <DataTemplate x:Key="BooleanTemplate">
                    <CheckBox IsChecked="False"></CheckBox>
                </DataTemplate>
                <local:MethodValueDataTemplateSelector x:Key="MethodValueTemplateSelector"
                                               StringTemplate="{StaticResource StringTemplate}"
                                               IntegerTemplate="{StaticResource IntegerTemplate}"
                                               BooleanTemplate="{StaticResource BooleanTemplate}"/>
            </uwpControls:DataGrid.Resources>

 <uwpControls:DataGrid.Columns>

                <uwpControls:DataGridTextColumn Header="First Column"  
                                                Binding="{Binding ParameterDataType, Mode=OneWay}"/>

                <uwpControls:DataGridTemplateColumn Header="Second Column">
                    <uwpControls:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ContentControl x:Name="MethodValueContentControl"
                                            Content="{Binding Path=.}"
                                            ContentTemplateSelector="{StaticResource MethodValueTemplateSelector}"></ContentControl>
                        </DataTemplate>
                    </uwpControls:DataGridTemplateColumn.CellTemplate>
                </uwpControls:DataGridTemplateColumn>
            </uwpControls:DataGrid.Columns>
        </uwpControls:DataGrid>

Here is my DataTemplate selector

public class MethodValueDataTemplateSelector : DataTemplateSelector
    {
        public DataTemplate StringTemplate { get; set; }
        public DataTemplate IntegerTemplate { get; set; }
        public DataTemplate BooleanTemplate { get; set; }

        protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
        {
            //I want to do something like if(DataContext.ParameterDataType=="Int") return IntegerTemplate etc
            return StringTemplate;
        }
    }

Here is my ViewModel

public class ServiceUtilityMethodsViewModel
{
        private ObservableCollection<VmServiceMethodsViewDataGridModel> _serviceMethodsData;
        public ObservableCollection<VmServiceMethodsViewDataGridModel> ServiceMethodsData
        {
            get => _serviceMethodsData;
            set => Set(ref _serviceMethodsData, value);
        }

        public ServiceUtilityMethodsViewModel(INavigationService navigationService) : base(navigationService)
        {
            PopulateServiceData();
        }

        private void PopulateServiceData()
        {
            ServiceMethodsData = new ObservableCollection<VmServiceMethodsViewDataGridModel>();
            ServiceMethodsData.Add(new VmServiceMethodsViewDataGridModel()
            {
                ParameterName = "Param1",
                ParameterDataType = "String"
            });
            ServiceMethodsData.Add(new VmServiceMethodsViewDataGridModel()
            {
                ParameterName = "Param2",
                ParameterDataType = "Int"
            });
            ServiceMethodsData.Add(new VmServiceMethodsViewDataGridModel()
            {
                ParameterName = "Param3",
                ParameterDataType = "bool"
            });
        }
    }
}

Here is my Model class

public class VmServiceMethodsViewDataGridModel : BindableBaseThreadSafe
    {
        private string _parameterName;
        private string _parameterDataType;

        public string ParameterName
        {
            get => _parameterName;
            set => Set(ref _parameterName, value);
        }
        public string ParameterDataType //I want the template selector to work based on this column.
        {
            get => _parameterDataType;
            set => Set(ref _parameterDataType, value);
        }
    }
nikhil
  • 1,578
  • 3
  • 23
  • 52

1 Answers1

1

You should assign the DataTemplateSelector to DataGridTemplateColumn.CellTemplateSelector and DataGridTemplateColumn.CellEditingTemplateSelector directly.

I didn't check the UWP version. I think the UWP DataGridTemplateColumn doesn't have this template selector properties. In this case you can stick to your current XAML, but don't forget to define a CellEditingTemplate too (e.g., replace the TextBlock with a TextBox for the CellEditingTemplate version - better use a TextBlock in your default CellTemplate as it looks better). The properties CellTemplate and CellEditingTemplate exist for sure in the UWP version.

XAML DataGrid definition

<uwpControls:DataGrid ItemsSource="{x:Bind ViewModel.ServiceMethodsData,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                      AutoGenerateColumns="False">
  <uwpControls:DataGrid.Resources>
    <DataTemplate x:Key="StringTemplate">
      <TextBox Width="150" Height="30" VerticalAlignment="Center" />
    </DataTemplate>
    <DataTemplate x:Key="IntegerTemplate">
      <controls:TextBoxNumeric Width="150" Height="30" VerticalAlignment="Center" />
    </DataTemplate>
    <DataTemplate x:Key="BooleanTemplate">
      <CheckBox IsChecked="False" />
    </DataTemplate>

    <local:MethodValueDataTemplateSelector x:Key="MethodValueTemplateSelector"
                                           StringTemplate="{StaticResource StringTemplate}"
                                           IntegerTemplate="{StaticResource IntegerTemplate}"
                                           BooleanTemplate="{StaticResource BooleanTemplate}" />
  </uwpControls:DataGrid.Resources>

  <uwpControls:DataGrid.Columns>
    <uwpControls:DataGridTextColumn Header="First Column"  
                                    Binding="{Binding ParameterDataType, Mode=OneWay}" />
    <uwpControls:DataGridTemplateColumn Header="Second Column"
                                        CellTemplateSelector="{StaticResource MethodValueTemplateSelector}"
                                        CellEditingTemplateSelector="{StaticResource MethodValueTemplateSelector}">
    </uwpControls:DataGridTemplateColumn>
  </uwpControls:DataGrid.Columns>
</uwpControls:DataGrid>

The DataTemplateSelector is also quite simple.
The parameters of the SelectTemplateCore override are the item and the item's container (which is a FrameWorkElement and a ContentControl most of the time).
The item is always the data model and the DataContext of the current row. In your case the item is of type VmServiceMethodsViewDataGridModel.

The container is the FrameWorkElement that wraps the model for rendering e.g. ListBoxItem. In your case the container should be of type DataGridRow.

Simply cast the item parameter to the appropriate type and evaluate it.

MethodValueDataTemplateSelector.cs

public class MethodValueDataTemplateSelector : DataTemplateSelector
{
  public DataTemplate StringTemplate { get; set; }
  public DataTemplate IntegerTemplate { get; set; }
  public DataTemplate BooleanTemplate { get; set; }

  protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
  {
    // Return if the control is not loaded yet and the item is therefore null    
    // or the item is of the wrong type
    if (!(item is VmServiceMethodsViewDataGridModel dataModel))
    {
      return null;
    }

    // I want to do something like: 
    // if(DataContext.ParameterDataType=="Int") return IntegerTemplate etc
    switch (dataModel.ParameterDataType)
    {
      case string value when value.Equals("Int", StringComparison.OrdinalIgnoreCase): 
        return IntegerTemplate;
      case string value when value.Equals("String", StringComparison.OrdinalIgnoreCase): 
        return StringTemplate;
      case string value when value.Equals("Bool", StringComparison.OrdinalIgnoreCase): 
        return BooleanTemplate;
      default: 
        return null;
    }
  }
}
BionicCode
  • 1
  • 4
  • 28
  • 44
  • Thank you it works fine in Wpf project but in UWP when i assign the cell editing template it throws an error. I will try using a simple listview rather than the toolkits DataGrid. – nikhil Apr 22 '20 at 20:55
  • What is the error message? Maybe we can fix it. But if the `ListView` is sufficient for you, then why bother - except for the sake of understanding. – BionicCode Apr 22 '20 at 20:58
  • Yea a simple listview should do. But I just wanted to try with a DataGrid. When i try to assign the CellEditingTemplate it says, failed to assign property to uwpToolKit.CellEditing Template. The inner exception is null. – nikhil Apr 22 '20 at 21:07
  • Also the same code I created a Wpf project and it works just fine.The only reason I am using a DataGrid is because it shows grid lines – nikhil Apr 22 '20 at 21:08
  • I just tried it out with a ListView and it works fine also. Its only the uwpToolkit's cellediting template. – nikhil Apr 22 '20 at 21:20
  • Maybe tomorrow I'll find time to check what's wrong with the UWP `DataGrid`. I will let you know in case I checked it and found some light. C you. – BionicCode Apr 22 '20 at 21:55
  • Sure thank you. I am pretty sure myself the uwpToolKit Datagrid CellEditingTemplate is buggy. Thats just my feeling. – nikhil Apr 22 '20 at 22:06
  • Can you please help me with this post https://stackoverflow.com/questions/61965743/textbox-inside-a-listview-bound-to-an-object-two-way-binding-dosent-work – nikhil May 23 '20 at 00:15
  • Hey @nikhil, nice to see you again. I checked your question and found it already answered. Do you still need help? – BionicCode May 23 '20 at 14:29
  • Hey @BionicCode thanks so much. I will try a different approach to my question writing a converter. Because the answer dosen't fit in completely to my use case it gives me an idea. I will try it and may be ask for help if I can't solve it. Thanks again – nikhil May 23 '20 at 16:21
  • Hey @BionicCode Could you please help me with my new problem. I have updated my post here, https://stackoverflow.com/questions/61965743/textbox-inside-a-listview-bound-to-an-object-two-way-binding-dosent-work – nikhil May 24 '20 at 02:44