Wpf Binding Bridge

I have a feeling this is a bug in wpf. Let me know what you guys think about this.

To keep things simple, I made a demo in .net 4.0

I have a ContentControl with Content bound to a Data and a ContentTemplate that contains a CheckBox binding to the content.

The problem is that the Ok property never works no matter how often I click the CheckBox.

It's like the CheckBox doesn't pass the new ViewModel value.

Take a look at this:

  <Window.Resources>
    <DataTemplate x:Key="dataTemplate">
      <CheckBox IsChecked="{Binding Path=., Mode=TwoWay}"/>
    </DataTemplate>
  </Window.Resources>

  <Grid>
    <ContentControl Content="{Binding Path=Ok, Mode=TwoWay}" ContentTemplate="{StaticResource dataTemplate}"/>
  </Grid>

      

This is my ViewModel

public MainWindow()
{
    InitializeComponent();

    ViewModel vm = new ViewModel();
    this.DataContext = vm;
}

public class ViewModel : INotifyPropertyChanged
{
    private string txt;

    public string Txt
    {
        get { return txt; }
        set { txt = value; this.OnPropertyChanged("Txt"); }
    }

    private bool ok;

    public bool Ok
    {
        get { return ok; }
        set { ok = value; this.OnPropertyChanged("Ok");}
    }


    private void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

      

Any ideas on how to fix this issue using ContentTemplate?

+3


source to share


4 answers


Your problem is a general problem with using value types. Data is bound to the first type(bool) (which is pretty unusual for this). Since it Binding.Source

has a type object

, your boolean code becomes boxed in object

. Any updates to this object in the box do not affect the original property on ViewModel

.

You can test this theory by replacing this boolean with a structure like this:

public struct MyStruct
{
    private bool _isChecked;
    public bool IsChecked
    {
        get { return _isChecked; }
        set { _isChecked = value; }
    }

    public ViewModel Parent { get; set; }
} 

      

and change the binding:

<CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}"/>

      

If you have set breakpoints on the getter and setter IsChecked

, you will see that the binding works. However, when you hit one of the breakpoints, try examining that value in the Immediate window:

? this.Parent.Ok.IsChecked

      



You should see that the property MyStruct

in the parent view model is not affected by data binding at all.


Complete test code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            ViewModel vm = new ViewModel();
            vm.Ok = new MyStruct { Parent = vm, IsChecked = false };
            this.DataContext = vm;
        }


    }

    public class ViewModel : INotifyPropertyChanged
    {
        private string txt;

        public string Txt
        {
            get { return txt; }
            set { txt = value; this.OnPropertyChanged("Txt"); }
        }

        private MyStruct ok;

        public MyStruct Ok
        {
            get { return ok; }
            set { ok = value; this.OnPropertyChanged("Ok"); }
        }


        private void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

    public struct MyStruct
    {
        private bool _isChecked;
        public bool IsChecked
        {
            get { return _isChecked; }
            set { _isChecked = value; }
        }

        public ViewModel Parent { get; set; }
    }
}

      

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
    <DataTemplate x:Key="dataTemplate">
      <CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}"/>
    </DataTemplate>
  </Window.Resources>

  <Grid>
    <ContentControl Content="{Binding Path=Ok, Mode=TwoWay}" ContentTemplate="{StaticResource dataTemplate}"/>
  </Grid>
</Window>     

      

+3


source


You can do it

<Window.Resources>
    <DataTemplate x:Key="dataTemplate">
      <CheckBox IsChecked="{Binding Path=Ok, Mode=TwoWay}"/>
    </DataTemplate>
</Window.Resources>

<Grid>
  <ContentControl Content="{Binding}" ContentTemplate="{StaticResource dataTemplate}"/>
</Grid>

      



in the example above, I bind content to the view model itself and then IsChecked of CheckBox to the Ok property.

+2


source


The problem is ContentControl

it won't pass it DataContext

to content.

You need to install DataContext

manually.

<CheckBox DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}" IsChecked="{Binding Path=., Mode=TwoWay}"/>

      

0


source


After some research (and after posting an irrelevant answer and then deleting it again) - and motivated by some discussion with Eren Ersรถnmez and Dev Hedgehog - the reason why two-way binding IsChecked="{Binding Path=., Mode=TwoWay}"

can't work has become clearer.

(Note: this answer does not provide a solution to the problem. Pushpraj's answer already provides a good solution.)

{Binding Path=., Source=SomeSource}

denotes a binding that directly binds to a binding source. Equivalent binding syntax {Binding Source=SomeSource}

.

However, bindings to the binding source itself cannot be two-way. Attempting to do this by specifying {Binding Source=SomeSource, Mode=TwoWay}

will throw an InvalidOperationException at runtime.

On the other hand, {Binding Path=., Source=SomeSource, Mode=TwoWay}

it won't throw an exception - but it still doesn't allow two-way binding for such data binding. Rather, it is only fooling around the bind checker / bug of the bindings of the bindings.

This has already been figured out and discussed in answers to other anchoring - related questions like Allon Guralnek's or HB's (and if it weren't for their answers and my discussion with Eren Ersenmez, I probably still wouldn't know what's going on here. ..).

This also means that the problem is not caused by the box type being the source of the binding per se, but rather by trying to use two-way binding from the binding path .

.


Why doesn't it make sense to have two-way bindings with an anchor string .

?

Below is a small example to demonstrate the problem. It also demonstrates that the problem is not limited to situations where the boxed value type is the binding source, and that it is limited to the bindings associated with the DataContext.

public class Demo
{
    public static string SourceString
    {
        get { return _ss; }
        set { _ss = value; }
    }
    private static string _ss = "Hello World!";
}

      

The string (reference type) provided by the static Demo.SourceString property will be used as the binding source in the following XAML:

<TextBox Text="{Binding Source={x:Static My:Demo.SourceString}, Mode=TwoWay}" />

      

(Note that the object provided by the My: Demo.SourceString property is the binding source; the property itself is not the binding source.)

This XAML above will throw an InvalidOperationException at runtime. However, using the equivalent XAML:

<TextBox Text="{Binding Path=., Source={x:Static My:Demo.SourceString}, Mode=TwoWay}" />

      

will not throw an exception at runtime, but the binding is nonetheless not a working two-way binding. The text entered in the text box will not be returned to the Demo.SourceString property. It can't - all binding knows that it has a string object as source and a binding path that is .

.

... but you just need to update the Demo.SourceString, which can't be too complicated, or?

Let's assume for a moment that two-way binding to a path .

, as shown in the XAML above, tries to work like two-way binding โ€” will it lead to meaningful behavior? What happens if the TextBox.Text property changes its value due to user input?

The binding will theoretically try to push the new string value back to the binding source by applying the binding path .

to the object that serves as the binding source (the "Hello World!" String). It doesn't make a lot of sense ... Well, maybe this would replace the original binding source (string "Hello World!") With the string from the TextBox.Text property - but that would be completely pointless as this change would be local and internal binding. The binding will fail to assign a new Demo.SourceString - unless someone knows a way to get a reference to the Demo.SourceString by applying the binding path .

to the string object containing "Hello World!" ., Therefore, two-way binding mode is not a valid option for bindings,which bind to the binding source itself.

(If anyone is confused about how the example above applies to binding using DataContext, for example {Binding Path=., Mode=TwoWay}

, just ignore the Demo class and replace any occurrences of Demo.SourceString in the text with DataContext.)

0


source







All Articles