" /> " /> "/>

Based on ComboBox selection, change another ComboBox to TextBox using DataTrigger inside DataGrid CellTemplate

64 views Asked by At

I have one DataGrid where I am using CellTemplate to define various Data columns. Like below

<DataGridTemplateColumn Header="Movie Source" Width="*">
      <DataGridTemplateColumn.CellTemplate>
           <DataTemplate>
              <ComboBox x:Name="cbMovieSource" Width="100" 
                      ItemsSource="{Binding Path=MovieSources, Mode=Twoway}" 
                      SelectedItem="{Binding Path=MovieSourceSelected, Mode=Twoway, UpdateSourceTrigger=PropertyChanged}" 
                      IsSynchronizedWithCurrentItem="False">
              </ComboBox>
           </DataTemplate>
       </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>

Now, I have 3/4 data columns in that DataGrid, Based on one Data column 'Movie Source'- ComboBox's value " Manual Entry", I have to change another Data column 'Movie Hall' ComboBox into TextBox to allow user enter data. I have used Data trigger to do that.

Problem is --

  1. While loading page initially its showing blank instead of default ComboBox -'Movie Hall'.

  2. And while I am changing Movie Source ComboBox's value to "Manual Entry" its not changing Movie Hall ComboBox into TextBox but if I click in that blank space TextBox appearing.

I am using Observable Collection for Item source and data loading is not a problem. But how I will get initially ComboBox but after selecting certain value from ComboBox, into TextBox is the issue here.

My Data trigger code is like below -

<DataGridTemplateColumn Header="Movie Hall" Width="*">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ContentControl>
                <ContentControl.Style>
                    <Style TargetType="av:ContentControl" >
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding ElementName=cbMovieSource, Path=SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                                <Setter Property="ContentTemplate">
                                    <Setter.Value>
                                        <DataTemplate>
                                            <ComboBox x:Name="cbMovieHall" Width="120" 
                                                      ItemsSource="{Binding MovieHalls, Mode=Twoway}" 
                                                      SelectedItem="{Binding MovieHallsSelected, Mode=Twoway, UpdateSourceTrigger=PropertyChanged}">      
                                            </ComboBox>
                                        </DataTemplate>
                                    </Setter.Value>
                                </Setter>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding Path=MovieSourceSelected}" Value ="Manual Entry">
                                <Setter Property="ContentTemplate">
                                    <Setter.Value>
                                        <DataTemplate>
                                            <TextBox Width="120"  Visibility="Visible" 
                                                Text="{Binding DataContext.TextA, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
                                        </DataTemplate>
                                    </Setter.Value>
                                </Setter>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </ContentControl.Style>
            </ContentControl>
       </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Initial Loading

enter image description here

After selecting value enter image description here

After clicking on blank space under Movie Hall header-

enter image description here

2

There are 2 answers

1
Joker On BEST ANSWER

I think now issue is not in any xaml code written here, everything is just fine with DataTrigger, CellTemplate etc. Because you mentioned after clicking its changing means trigger working but UpdateSourceTrigger = PropertyChanged not working that's why Trigger is firing after manual click.

You also mentioned all other columns and this column also working fine with Data population that means all your ComboBox columns are having item source with collection which is declared in mvvm style, INotifyPropertyChanged. And I see in comment that selected property is string and item source is collection of string.

You have not wrote any c# code for those properties but with the problem I am assuming you have observable collections with data class not direct string collection. Please make sure your c# code is like below in ViewModel class -

 private ObservableCollection<MovieData> _dataGridCollection;
    public ObservableCollection<MovieData> DataGridCollection 
    {
          get => _dataGridCollection;
          set
          {
              var changed = DataGridCollection != value;
              if (changed)
              {
                  _dataGridCollection = value;
                  NotifyPropertyChanged($"DataGridCollection ");
               }
           }
    }

And Model class like below -

 Public class MovieData : INotifyPropertyChanged
    {
        private string _movieSourceSelected;
        public string MovieSourceSelected
        {
            get => _movieSourceSelected;
            set
            {
               _movieSourceSelected = value;
               NotifyPropertyChanged("MovieSourceSelected");
             }
        }
    }

And for Xaml part, @Étienne Laneville wrote excellent code there, nothing is wrong in that part but still for answer completion adding to his answer with few minor modifications like Direct Visibility set in control itself and again inside style setter will prevent data trigger to work properly and Grid to StackPanel and Hidden to Collapsed as Hidden always occupies the space and StackPanel puts controls with VerticalOrientation default with one control after another not with same space, which is required here and Grid will put both control in same place..

<DataGridTemplateColumn Header="Movie Hall" Width="*">
    <av:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <StackPanel>
                <ComboBox Width="120"     
                          IsSynchronizedWithCurrentItem="False"                                  
                          ItemsSource="{Binding MovieHalls, Mode=Twoway}"
                          SelectedItem="{Binding MovieHallsSelected, Mode=Twoway, UpdateSourceTrigger=PropertyChanged}">
                            <ComboBox.Style>
                                <Style TargetType="ComboBox">
                                    <Setter Property="Visibility" Value="Visible" />
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding Path=MovieSourceSelected,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                                         Value="Manual Entry">
                                                <Setter Property="Visibility" Value="Collapsed" />
                                            </DataTrigger>
                                        </Style.Triggers>
                                </Style>
                            </ComboBox.Style>
                </ComboBox>
                 <TextBox Width="120"                                                   
                          Text="{Binding DataContext.TextA, RelativeSource={RelativeSource AncestorType=av:DataGridRow}}">
                            <TextBox.Style>
                                <Style TargetType="TextBox">
                                    <Setter Property="Visibility" Value="Collapsed" />
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding Path=MovieSourceSelected,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                                                         Value="Manual Entry">
                                                    <Setter Property="Visibility"  Value="Visible" />
                                            </DataTrigger>
                                        </Style.Triggers>
                                </Style>
                            </TextBox.Style>
                </TextBox>
             </StackPanel>
        </DataTemplate>
     </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Edit after answer acceptance :Long story short, your ComboBox was not changing into TextBox because your trigger was not firing automatically. And your trigger was not firing automatically because your UpdateSourceTrigger = PropertyChanged was not working. And UpdateSourceTrigger was not working because your c# code was missing INotifyPropertyChanged implementation with that bind property of Selected Item of Source ComboBox.

9
Étienne Laneville On

I think you only need one DataTrigger, the second one. Use the Setter from the first DataTrigger by itself in the Style (this will be the default), and then have the Style.Triggers section with just the second DataTrigger:

<DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
        <ContentControl>
            <ContentControl.Style>
                <Style TargetType="av:ContentControl">
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <ComboBox x:Name="cbMovieHall"
                                            Width="120"
                                            ItemsSource="{Binding MovieHalls, Mode=Twoway}"
                                            SelectedItem="{Binding MovieHallsSelected, Mode=Twoway, UpdateSourceTrigger=PropertyChanged}">
                                </ComboBox>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Path=MovieSourceSelected}"
                                        Value="Manual Entry">
                            <Setter Property="ContentTemplate">
                                <Setter.Value>
                                    <DataTemplate>
                                        <TextBox Width="120"
                                                    Visibility="Visible"
                                                    Text="{Binding DataContext.TextA, RelativeSource={RelativeSource AncestorType=DataGridRow}}" />
                                    </DataTemplate>
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ContentControl.Style>
        </ContentControl>
    </DataTemplate>
</DataGridTemplateColumn.CellTemplate>

The first Setter in the Style will be applied by default. Then, if the DataTrigger condition is met, the Setter in that DataTrigger will also be applied, overwriting the first Setter and, in this case, setting the ContentTemplate to the DataTemplate with a TextBox in it.

You can also simplify things by having the ComboBox and TextBox handle hiding themselves based on the DataTrigger instead of using a ContentControl:

<DataGridTemplateColumn Header="Movie Hall"
                        Width="*">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Grid>
                <ComboBox Width="120"
                          ItemsSource="{Binding MovieHalls, Mode=Twoway}"
                          SelectedItem="{Binding MovieHallsSelected, Mode=Twoway, UpdateSourceTrigger=PropertyChanged}">
                    <ComboBox.Style>
                        <Style TargetType="ComboBox">
                            <Setter Property="Visibility"
                                    Value="Visible" />
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Path=MovieSourceSelected}"
                                                Value="Manual Entry">
                                    <Setter Property="Visibility"
                                            Value="Hidden" />
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </ComboBox.Style>
                </ComboBox>
                <TextBox Width="120"
                            Visibility="Visible"
                            Text="{Binding DataContext.TextA, RelativeSource={RelativeSource AncestorType=DataGridRow}}">
                    <TextBox.Style>
                        <Style TargetType="TextBox">
                            <Setter Property="Visibility"
                                    Value="Hidden" />
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Path=MovieSourceSelected}"
                                                Value="Manual Entry">
                                    <Setter Property="Visibility"
                                            Value="Visible" />
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </TextBox.Style>
                </TextBox>
            </Grid>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

This XAML has a ComboBox that is Visible by default and set to Hidden when MovieSourceSelected is Manual Entry. The TextBox is Hidden by default and set to Visible with Manual Entry.

Regarding the Binding used for your Movie Source ComboBox, make sure it uses SelectedValue and SelectedValuePath if you are using a String as your Dependency Property:

<ComboBox SelectedValue="{Binding MovieSourceSelected, Mode=TwoWay}" SelectedValuePath="Content">
    <ComboBoxItem Content="Some option" />
    <ComboBoxItem Content="Manual Insertion" />
    <ComboBoxItem Content="Some other option" />
</ComboBox>

As a side note, you should not have to name your ComboBoxes (cboMovieSource and cbMovieHall) since they are working with bindings. You'd name them if you needed to access them in code-behind but from your XAML it doesn't look like you need to.