3

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

To keep everything simple I made demo example in .net 4.0

I have a ContentControl with Content bound to Data and ContentTemplate which holds a CheckBox bound to Content.

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

As if CheckBox doesn't pass the new value to ViewModel.

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 how to fix this issue with ContentTemplate?

dev hedgehog
  • 8,698
  • 3
  • 28
  • 55
  • If you change the datatemplate to a controltemplate and bind the contentcontrols template property to the controltemplate I think the datacontext is preserved. In your case it looks like the datacontext is not preserved – JKennedy Aug 05 '14 at 08:45
  • Hmmm I cannot do that. This is just a demo. It shows that there is generally a bug with ContentTemplate. I cannot change anything! I have to stick to ContentTemplate. Is there another way? – dev hedgehog Aug 05 '14 at 08:48
  • I'm pretty sure this isn't a WPF bug. Do you see any binding errors in your debug output? – Dan Puzey Aug 05 '14 at 08:49
  • [MSDN: Binding Declarations Overview](http://msdn.microsoft.com/en-us/library/ms752300.aspx) says Optionally, a period (.) path can be used to bind to the current source. For example, Text="{Binding}" is equivalent to Text="{Binding Path=.}". – pushpraj Aug 05 '14 at 08:51
  • @pushpraj: I'm not sure what the relevance of that is to the question. Can you explain? – Dan Puzey Aug 05 '14 at 08:56
  • @pushpraj What do you mean??? Current Source of ContentControl control is Content and Content futhermore binds to Data. – dev hedgehog Aug 05 '14 at 08:56
  • @elgonzo - Please turn these comments into a proper answer. Comments will get out of order and are hardly readable – Emond Aug 05 '14 at 09:10
  • @ErnodeWeerd, yeah, you are perhaps right... :) –  Aug 05 '14 at 09:11
  • 1
    @DanPuzey What I mean to say that if the code in question `IsChecked="{Binding Path=.}"`is written as `IsChecked="{Binding}"` would fail because a two way binding require a path or xpath defined. however by setting `Path=.` the validation/warning is suppressed leading to such behavior – pushpraj Aug 05 '14 at 09:22
  • @elgonzo write your comment as answer please then we can talk about it – dev hedgehog Aug 05 '14 at 09:30
  • @devhedgehog, FYI: I decided to delete my answer. While it correctly describes the behavior of the ContentPresenter, it really doesn't matter. Fixing the binding(s) to *not* use boxed value types as their source(s) makes my explanations actually a non-issue. Well, that's the power of a single wrong assumption, right there :) –  Aug 05 '14 at 12:00
  • But your answer was the right one :) my binding werent using value types. – dev hedgehog Aug 05 '14 at 13:40
  • @devhedgehog, Eren's answer is closer to the actual root of the problem, and while editing my answer i felt like it becoming more or more like a duplicate of his answer while the bulk of my original answer became less and less relevant, hence i deleted it. In case you wonder what my wrong assumption was all about you can invite me into chat and i will gladly explain ;) –  Aug 05 '14 at 17:39
  • But I wasn'tusing value type as source. The issue was that I was binding to DataContext and not to Content. Content was bound too ViewModel but not DataContext. Therefore when explicitly bound to Content it worked as bridge to ViewModel. – dev hedgehog Aug 06 '14 at 06:14

4 Answers4

3

Your problem is a common problem related to the use of value types. You're data binding your checkbox to a primitive value type (bool) (which is a pretty uncommon thing to do). Since Binding.Source is of type object, your boolean is getting boxed into an object. Any updates on that boxed object has no effect on the original property on ViewModel.

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

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

    public ViewModel Parent { get; set; }
} 

and change your binding:

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

If you put breakpoints on the IsChecked getter and setter, you will see that the binding works. However, when you hit one of the break points, try to investigate this value in the Immediate Window:

? this.Parent.Ok.IsChecked

You should see that the MyStruct property on the parent view model is not being affected by the databinding at all.


Full 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>     
Eren Ersönmez
  • 38,383
  • 7
  • 71
  • 92
  • What you are explaining is simply a side-effect of using a struct -- but where did the O/P use a struct as value for a DataContext? While it generally speaking is indeed a 'trap for young players', it is not relevant regarding the O/P's question... –  Aug 05 '14 at 09:55
  • @elgonzo the problem would be general to all value types, not just structs. he is data-binding to `ViewModel.Ok` which is a value type. I mention this struct as an example to test this theory, since he can put breakpoints on the getter/setter and have a reference to the parent object. – Eren Ersönmez Aug 05 '14 at 09:59
  • No... look at your example: MyStruct (as in your answer) is a property (struct type) -- you bind against it. then another binding binds against a **property of the struct**. The problem you are explaining is not about value types in general, it is about binding against **properties of structs**. There is no conclusion to be made regarding the binding against ViewModel.Ok, since ViewModel is **not** a struct (i.e., Ok not being a property of a struct) –  Aug 05 '14 at 10:05
  • @elgonzo You're saying "No..." to what? Are you suggesting it's ok to use a value type like (a bool or an int) as the binding _Source_, as long as it is not a struct? – Eren Ersönmez Aug 05 '14 at 10:14
  • I am not sure whether i understand your last comment correctly. The sources of the bindings used by the O/P is always the DataContext property - in the binding used at the ContentControl the DataContext contains a reference **value**, regarding the binding used within the template DataContext contains a bool **value**... –  Aug 05 '14 at 10:19
  • @elgonzo exactly, now.. Since the binding `IsChecked="{Binding Path=., Mode=TwoWay}"` doesn't specify a `Source=..`, it will use the DataContext as the source, right? And what is _type_ of `FrameworkElement.DataContext`? It's `System.Object`. Therefore, when you say "binding used within the template DataContext contains a bool value", it`s actually a _boxed_ bool value (since it's of type object, it cannot carry a boolean value without boxing it first). And as you can image, even if the binding works on the boxed object, it will have no effect on the original boolean property on the ViewModel. – Eren Ersönmez Aug 05 '14 at 10:28
  • Okay, i realized you were emphasizing boxing/unboxing issues and that i was more focused on structs. However, your perseverance made me doing a quick check -- and indeed there are issues regarding boxing/unboxing - which puzzles me, since i have some XAML that binds against the Tag property to store/update/propagate int values. I think i need to take a closer look at why these Tag shenanigans apparently work while the simple example i just made now for easy debugging doesn't. Hmm, strange... Anyway, many thanks for the clarification and your peserverance :) –  Aug 05 '14 at 10:48
  • Could you guys upload the demo code you testing against? I would like to see that side effect. Pls – dev hedgehog Aug 05 '14 at 10:52
  • @devhedgehog, it's pretty simple. Do this instead of using your ContentControl: ``. Then, also put a textblock somewhere to observe the DataContext of the Checkbox, like ``... it's not working (as Eren tried to explain to me :) ) –  Aug 05 '14 at 10:57
  • Sooo to sum up binding takes a copy when working with structs/value types and therefore you do not get updates in your original value??? no reference is being used? – dev hedgehog Aug 05 '14 at 11:02
  • @devhedgehog if you use the value type as the binding _Source Object_, yes. There is no problem with using a value type as a binding source/target _Property_. – Eren Ersönmez Aug 05 '14 at 11:05
  • means when i use value type as source and try set value on it, a copy will be internally created since unboxing/unboxing happens??? is that what you trying to tell me in plain english :) – dev hedgehog Aug 05 '14 at 11:08
  • Oh my, i had a stupid brain fail. Of course, a binding without specifying a Source explicitly uses the object in the DataContext property as its source (it does *not* reference the DataContext property itself). So, going with the theme of boolean values, binding against a DataContext which contains such boolean value would be akin to specifying the binding source as `Binding.Source = (object) thatBoolValue;`. Dang... am i stupid today, or what? –  Aug 05 '14 at 11:12
  • but wait guys let me ask you something :) – dev hedgehog Aug 05 '14 at 11:15
  • Why does still works: and Content is bool value/value type and CheckBox is asking for Content. Still it works even though value is being copied. It works for me at least hehe. That is my workaround to this question. – dev hedgehog Aug 05 '14 at 11:16
  • @devhedgehog, the sources of both bindings are/refer to objects. The source of the binding in the ContentControl is the object refered to by DataContext (your viewmodel). The source of the binding in the checkbox is the ContentControl (which is an object) -- None of these two bindings has a source which would be a value type, for both cases the binding sources are objects - and the bindings work on properties of these objects... –  Aug 05 '14 at 11:19
  • So again for me to sum up, that strange behavior with value type only occurs when the SOURCE is being used, right? Such as in {Binding Path=.} – dev hedgehog Aug 05 '14 at 11:26
  • that is the magic of the wpf binding engine. Since there are two bindings, which are both two-way, when the IsChecked binding changes the Content value, the other binding on the ContentControl also updates its source. So you have two chained bindings now. And as @elgonzo explained already, none of those bindings have a value type as the source object. – Eren Ersönmez Aug 05 '14 at 11:28
2

you can do this way

<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 above example I bind the Content to the view model itself and then IsChecked of CheckBox to the Ok property.

pushpraj
  • 13,458
  • 3
  • 33
  • 50
  • Well this is another constillation. This works. However I would like to know why aint my constillation working? – dev hedgehog Aug 05 '14 at 09:02
  • the issue with the approach is inability to determine the target object. binding works on dependency framework to propagate the changes if the property is a dependency property otherwise it uses reflection. so when you bind the property as source of the binding, extension is unable to determine the target object to perform the set value operation. typically a two way binding requires a path but setting Path=. suppress the warning hence leading to such inconsistencies. – pushpraj Aug 05 '14 at 09:18
0

The Problem is that ContentControl won't pass it's DataContext to the Content.

You need to set the DataContext manually.

<CheckBox DataContext="{Binding DataContext,RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}" IsChecked="{Binding Path=., Mode=TwoWay}"/>
Johannes Wanzek
  • 2,825
  • 2
  • 29
  • 47
0

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

(Side note: This answer does not contain a solution to the problem. Pushpraj's answer already provides a nice solution.)

{Binding Path=., Source=SomeSource} denotes a binding which binds to the binding source directly. An equivalent binding syntax is {Binding Source=SomeSource}.

However, bindings to the binding source itself cannot be two-way. Trying so by specifying {Binding Source=SomeSource, Mode=TwoWay} will cause an InvalidOperationException during run-time.

On the other hand, {Binding Path=., Source=SomeSource, Mode=TwoWay} will not produce an exception - but it still does not enable a two-way binding for such a data binding. Rather, it only fools the binding validation/error handling mechanism of the binding engine.

This has been already found out and discussed in answers to other binding-related questions, such as the answers by Allon Guralnek or by H.B. (and were it not for their answers and my discussion with Eren Ersönmez, i would probably still be clueless of what is going on here...).

This also means that the issue is not caused by a boxed value type being a binding source per se, but rather due to trying to use a two-way binding with the binding path ..


Why does it make no sense to have two-way bindings with binding path .?

Below is a small example demonstrating the issue. It also demonstrates that the problem is not restricted to situations where a boxed value type is being the binding source, nor that it is restricted to bindings involving DataContext.

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

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

<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 produce an InvalidOperationException at run-time. However, using the equivalent XAML:

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

will not produce an exception during run-time, but the binding is nevertheless not a working two-way binding. Text entered into the text box will not be promoted back to the Demo.SourceString property. It can't -- all the binding knows is that it has a string object as source and a binding path which is ..

...but it would just need to update Demo.SourceString, that can't be too difficult, or?

Assume for a moment that a two-way binding with a path . as shown in the XAML above would try to work as a two-way binding - would it result in meaningful behavior? What would happen when the TextBox.Text property has changed its value due to the user inputting text?

The binding would theoretically try to promote the new string value back to the binding source by applying the binding path . onto the object that serves as the binding source (the string "Hello World!"). That does not make much sense... Well, it could perhaps replace the original binding source (the "Hello World!" string) with the string from the TextBox.Text property - but this would be rather meaningless as this change would be local and internal to the binding only. The binding would not be able to assign the new string to Demo.SourceString -- unless someone knows a way of how to obtain a reference to Demo.SourceString by applying the binding path . to a string object containing "Hello World!". Hence, two-way binding mode is not a feasible option for bindings which bind to the binding source itself.

(If somebody is confused about how the example above would apply to a binding using the DataContext like {Binding Path=., Mode=TwoWay}, just ignore the class Demo and substitute any occurences of Demo.SourceString in the text with DataContext.)

Community
  • 1
  • 1