Search code examples
wpfcomboboxbindingdatagrid

Unable to Bind ComboBox to DataGrid Column in WPF


I am trying to bind a ComboBox to a Field that is in a DataGrid and am unable to get it to update.

Here is the ComboBox from the xaml:

<ComboBox Grid.Row="6" Grid.Column="1" x:Name="cmbBedArea" HorizontalAlignment="Left" VerticalAlignment="Center" SelectedValue="{Binding ElementName=gridUsers, Path=SelectedValue.BedArea, Mode=OneTime}" Width="200"/>

Here is the full xaml file:

<Window x:Class="WinPharm.Net.frmDbUsers"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WinPharm.Net"
        mc:Ignorable="d"
        WindowStartupLocation="CenterScreen"
        Title="WinPharm User Management" Height="450" Width="800">

    <Window.Resources>
        <Style x:Key="spacerRow" TargetType="RowDefinition">
            <Setter Property="Height" Value="10"/>
        </Style>
    </Window.Resources>

    <Grid>
        <!--Users Grid-->
        <DataGrid
            x:Name="gridUsers" 
            VerticalAlignment="Top" 
            Height="150" 
            Margin="10,10,10,0" 
            IsReadOnly="True" 
            AutoGenerateColumns="False" 
            GridLinesVisibility="All" 
            AlternatingRowBackground="LightGray"
            SelectionMode="Single"
            SelectionUnit="FullRow" 
            Loaded="gridUsers_Loaded" 
            SelectionChanged="gridUsers_SelectionChanged"            
            >
            <DataGrid.Columns>
                <DataGridTextColumn Header="" Width="20"/>
                <DataGridTextColumn x:Name="LoginId" Header="Id" Width="150" Binding="{Binding LoginID}"/>
                <DataGridTextColumn x:Name="Name" Header="Name" Width="200" Binding="{Binding Name}"/>
                <DataGridTextColumn x:Name="BedArea" Header="Bed Area" Width="200" Binding="{Binding BedArea}"  />
            </DataGrid.Columns>
        </DataGrid>
        <!--Form Fields-->
        <Grid Margin="10,175,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Height="Auto" Width="400">
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition Style="{StaticResource spacerRow}"></RowDefinition>
                <RowDefinition></RowDefinition>
                <RowDefinition Style="{StaticResource spacerRow}"></RowDefinition>
                <RowDefinition></RowDefinition>
                <RowDefinition Style="{StaticResource spacerRow}"></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="80"></ColumnDefinition>
                <ColumnDefinition Width="280"></ColumnDefinition>
            </Grid.ColumnDefinitions>

            <Label Grid.Row="0" Grid.Column="0" Content="Login Id:" HorizontalAlignment="Right" VerticalAlignment="Top"/>
            <TextBox IsEnabled="False" Grid.Row="0" Grid.Column="1" x:Name="txtLoginId" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding ElementName=gridUsers, Path=SelectedItem.LoginID}" Width="200" MaxLength="50"/>

            <Label Grid.Row="2" Grid.Column="0" Content="Name:" HorizontalAlignment="Right" VerticalAlignment="Top"/>
            <TextBox Grid.Row="2" Grid.Column="1" x:Name="txtName" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding ElementName=gridUsers, Path=SelectedItem.Name, Mode=OneWay}" Width="200" MaxLength="50"/>

            <Label Grid.Row="4" Grid.Column="0" Content="External Id:" HorizontalAlignment="Right" VerticalAlignment="Top"/>
            <TextBox Grid.Row="4" Grid.Column="1" x:Name="txtExternalId" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding ElementName=gridUsers, Path=SelectedItem.ID_External, Mode=OneWay}" Width="200" MaxLength="50"/>

            <Label Grid.Row="6" Grid.Column="0" Content="Bed Area:" HorizontalAlignment="Right" VerticalAlignment="Top"/>
            <ComboBox Grid.Row="6" Grid.Column="1" x:Name="cmbBedArea" HorizontalAlignment="Left" VerticalAlignment="Center" SelectedValue="{Binding ElementName=gridUsers, Path=SelectedValue.BedArea, Mode=OneTime}" Width="200"/>
        </Grid>
        <!--Form Buttons-->
        <Grid HorizontalAlignment="Center" VerticalAlignment="Bottom">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="120"></ColumnDefinition>
                <ColumnDefinition Width="120"></ColumnDefinition>
                <ColumnDefinition Width="120"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Button x:Name="btnAdd" Grid.Column="0" Content="Add" Height="40" Width="100" Margin="10,0,0,10" Click="btnAdd_Click"></Button>
            <Button x:Name="btnDelete" Grid.Column="1" Content="Delete" Height="40" Width="100" Margin="10,0,0,10" Click="btnDelete_Click"></Button>
            <Button x:Name="btnUpdate" Grid.Column="2" Content="Update" Height="40" Width="100" Margin="10,0,0,10" Click="btnUpdate_Click"></Button>
        </Grid>
    </Grid>
</Window>

Any idea why the ComboBox does not get updated?

I put this into the code behind and was able to get it updating, but would like to do so without this code.

        private void gridUsers_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var drv = ((DataRowView)((DataGrid)sender).SelectedItem).Row;
            if (drv != null)
            {
                cmbBedArea.SelectedValue = drv["BedArea"];
            }
        }

Solution

  • Maybe you confuse BindingMode.OneTime with BindingMode.OneWay.

    BindingMode.OneTime means the binding will read the binding source just one time (hence the name of the binding mode). Any following updates of the data source will be ignored.
    BindingMode.OneWay on the other hand will track any binding source changes for the lifetime of the Binding object.

    BindingMode.OneTime is only used when you don't expect the data source to change (or you are only interested in the initial value). This will improve the application's performance as the binding engine does not need to monitor the binding source in order to detect changes.

    "I am also unclear why the default mode works and the OneTime does not."

    The default binding mode of the ComboBox.SelectedValue property is BindingMode.TwoWay - that's why the default works. It updates binding source and target (two way. The majority of dependency properties default to BindingMode.OneWay).
    At this point you should have understood that BindingMode.OneTime will allow the target (ComboBox) to update only once - a one time binding.

    "however I would prefer that the grid does not get updated upon change of the ComboBox."

    In this case you must change the Binding.Mode property explicitly to BindingMode.OneWay. This way the ComboBox will only receive changes but won't send changes. Remember, the default for the SelectedValue property is BindingMode.TwoWay which will of course allow the ComboBox to update the DataGrid.
    BindingMode.OneWay is your configuration value.

    <!-- Unless you also define the 'SelectedValuePath' 
         you should use the 'SelectedItem' property instead of 'SelectedValue' -->
    <ComboBox SelectedItem="{Binding Mode=OneWay, Path=SelectedItem.BedArea, ElementName=gridUsers}" />
    
    <!-- Alternative version using 'SelectedValuePath' and 'SelectedValue'-->
    <ComboBox SelectedItem="{Binding Mode=OneWay, Path=SelectedValue, ElementName=gridUsers}" />
    <DataGrid x:Name="gridUsers"
              SelectedValuePath="BedArea" />       
    

    In general, if you want updates (binding target and/or binding source): set Binding.Mode to BindingMode.OneWay, BindingMode.TwoWay or BindingMode.OneWayToSource.
    No updates: BindingMode.OneTime.

    See BindingMode.