Search code examples
c#wpfxamlmultibindingimultivalueconverter

WPF Multibinding: OneWayToSource binding from TextBox updated via another binding doesnt work?


I have a DataGrid bound to the People collection. Also I have a TextBox that should accept the Name value from the selected row. User can then edit the value or can leave it as is. The key point is: the text shown in the TextBox no matter whether it originates from collection or user typing must be propagated to the property NewName.

I've set two bindings for the NewNameTextBox: OneWay'ed to the CollectionView behind the DataGrid, and OneWayToSource'ed to the property:

<Window.Resources>
    <CollectionViewSource x:Key="PeopleCollection" 
         Source="{Binding Path=People, Mode=OneWay}" />
    <local:ConverterNewNamePrefill x:Key="ConverterNewNamePrefill" />
</Window.Resources>
<Grid>
    <StackPanel>
        <DataGrid ItemsSource="{Binding Source={StaticResource PeopleCollection}}"
                  AutoGenerateColumns="True"
                  IsReadOnly="True"
                  Margin="10">
        </DataGrid>
        <StackPanel Orientation="Horizontal" Margin="10">
            <TextBox>
                <TextBox.Text>
                    <MultiBinding Converter="{StaticResource ConverterNewNamePrefill}" >
                        <Binding Source="{StaticResource PeopleCollection}" Path="Name" Mode="OneWay" />
                        <Binding Path="NewName" Mode="OneWayToSource" />
                    </MultiBinding>
                </TextBox.Text>
            </TextBox>
        </StackPanel>
    </StackPanel>
</Grid>

I suppose the property should be updated when user changes selection in the DataGrid, but this doesn't happen. The TextBox gets updated and shows the selected Name value, but the property bound via OneWayToSource remains unchanged.

If the user types into the TextBox, the property gets updated as expected.

So the question is how can I update a property from both the sources via multi-bound TextBox without code behind view?

Here is the code behind window:

public class Person
{
        public string Name { get; set; }
        public string Surname { get; set; }
}

public partial class MainWindow : Window
{

    public MainWindow()
    {
        InitializeComponent();
    }

    private ObservableCollection<Person> _people = new ObservableCollection<Person> {
        new Person() {Name = "Mitchell", Surname = "Sofia" },
        new Person() {Name="Bush", Surname="Ethan" },
        new Person() {Name="Ferrero", Surname="Emma" },
        new Person() {Name="Thompson", Surname="Aiden" }
    };
    public ObservableCollection<Person> People => _people;

    public string NewName { get; set; } = "Jackson";
}

public class ConverterNewNamePrefill : IMultiValueConverter
{

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return values[0]; 
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return new[] { value, value };
    }
}

The converter's method ConvertBack() is called only when user types, but not when the TextBox.Text updated from collection.

Thank you!


Solution

  • This is just how bindings work. The source or sources are not updated unless the target changes by means other than the binding itself. I.e. it's assumed that if the target was just updated from the source(s), then the source(s) is(are) already up-to-date and do not need updating.

    Without more details it's difficult to know for sure what you want. But it seems like you might either want for NewName to actually be the target of a second binding, where the source is the same Name property being used as the source for the TextBox.Text property, or you want subscribe to the TextBox.TextChanged event and your handler explicitly write back the value to the NewName property when that event is raised.

    In the former case, you'll have to make NewName a dependency property of MainWindow. That's a complication you may or may not want to deal with. If not, then I'd recommend the latter approach.