Having the xaml below in MainWindow.xaml:
<Window x:Class="TestDependency.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"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Name="someLabel" Grid.Row="0" Content="{Binding Path=LabelText}"></Label>
<Button Grid.Row="2" Click="Button_Click">Change</Button>
</Grid>
</Window>
And the following code behind in MainWindow.xaml.cs:
public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register("LabelText", typeof(String), typeof(MainWindow));
public int counter = 0;
public String LabelText
{
get
{
return (String)GetValue(LabelTextProperty);
}
set
{
SetValue(LabelTextProperty, value);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LabelText = "Counter " + counter++;
}
I would have thought that the default DataContext is the code behind. But I'm forced to specify the DataContext. Which DataContext is the default? Null? I would have thought that the code behind would have been (as is the same class).
And as in this sample I'm using the code behind to modify the content of the Label, could I use directly:
someLabel.Content = "Counter " + counter++;
I will expect that being the code behind, it shouldn't have the UI update problem that you have if the DataContext is in a different class.
Yes, the default value of DataContext is null, here is how it's declared in FrameworkElement class -
public static readonly DependencyProperty DataContextProperty =
DependencyProperty.Register("DataContext", typeof(object),
FrameworkElement._typeofThis,
(PropertyMetadata) new FrameworkPropertyMetadata((object)null,
FrameworkPropertyMetadataOptions.Inherits,
new PropertyChangedCallback(FrameworkElement.OnDataContextChanged)));
FrameworkPropertyMetadata takes first param for default Value of property.
As it gets inherited by all the child controls your lable's DataContext remains null unless you specify the window data context.
and you can use someLabel.Content = "Counter " + counter++; in codebehind to set labels content; as such it's perfectly fine to access your controls in code behind.
Since you are binding a property of a Label, unless you specify a different binding source somehow the binding engine assumes that LabelText is a property on that class. It cannot magically determine that because the Label is a descendant of a MainWindow the binding source should be that window, which is why you need to explicitly declare it.
It's important to note that the concepts of "data context" and "binding source" are distinct: DataContext is one way to specify the binding source, but there are also others.
Related
I have the following strange (for me) situation
A ListBox is bound (as Source) to a Label with OneWay Mode, i.e. ListBox is read-only.
The Label is then bound to a ComboBox with TwoWay binding
ListBox --> Label <--> ComboBox - arrows denote binding mode
The strange thing is that when the program starts and the user selects through the list in the ListBox, all 3 controls behave as expected.
But as soon as one index is chosen from Combobox, the Label continues to work properly (is updated by the Combo), but the OneWay binding to ListBox disappears (is null) and the ListBox cannot update the Label any more.
It seems to me that when Label Content is set by other means besides the OneWay binding (as here with the Combo updating or maybe with a ValueConverter), this binding is cleared by WPF.
The other strange behavior is that if this OneWay binding between ListBox and Label is turned into a TwoWay one, then everything works perfectly.
The question is what am I doing wrong, or if this is the normal behavior, where could I find relevant documentation.
Please find below simplified code and XAML demonstrating the case.
My workaround is to set the Label Content with code in ListBox_SelectionChanged.
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace Test_Chained_controls
{
public partial class MainWindow : Window
{
public class ComboItems
{
public int iDX { get; set; }
public string sDesc { get; set; }
public ComboItems(int a, string b)
{
iDX = a;
sDesc = b;
}
}
public class ListItems
{
public int iLDX { get; set; }
public ListItems(int a)
{
iLDX = a;
}
}
public List<ListItems> intList = new List<ListItems>();
public List<ComboItems> idx_StrList = new List<ComboItems>();
public MainWindow()
{
InitializeComponent();
intList.Add(new ListItems(0));
intList.Add(new ListItems(1));
intList.Add(new ListItems(2));
intList.Add(new ListItems(3));
idx_StrList.Add(new ComboItems(0, "Zero"));
idx_StrList.Add(new ComboItems(1, "One"));
idx_StrList.Add(new ComboItems(2, "Two"));
idx_StrList.Add(new ComboItems(3, "Three"));
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
listBox.ItemsSource = intList;
comboBox.ItemsSource = idx_StrList;
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//// Set Label Content in case of OneWay
// var binding = BindingOperations.GetBinding(label, Label.ContentProperty);
// if (binding != null)
// {
// if (binding.Mode == BindingMode.OneWay)
// {} // Binding set - do nothing
// }
// else label.Content = listBox.SelectedItem;
}
}
}
XAML
<Window ... normal stuff
xmlns:local="clr-namespace:Test_Chained_controls"
mc:Ignorable="d"
Title="MainWindow" Height="182" Width="500" Loaded="Window_Loaded">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="140"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<Label Content="ListBox" Grid.Row="0" Grid.Column="0" Margin="20,10,0,0" />
<Label Content="Label" Grid.Row="0" Grid.Column="1" Margin="20,10,0,0" />
<Label Content="ComboBox" Grid.Row="0" Grid.Column="2" Margin="20,10,0,0" />
<ListBox x:Name="listBox" Grid.Row="1" Grid.Column="0" Margin="0"
DisplayMemberPath="iLDX"
SelectedIndex="0"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="ListBox_SelectionChanged"/>
<Border BorderThickness="1" Grid.Row="1" Grid.Column="1" Height="30"
Margin="20,20,0,0" BorderBrush="#FFACACAC" >
<!-- *********** Label with Mode=OneWay or TwoWay *********** -->
<Label x:Name="label" Width="100" Height="25"
Content="{Binding ElementName=listBox,
Path=SelectedItem.iLDX, Mode=OneWay }" />
</Border>
<ComboBox x:Name="comboBox" Grid.Row="1" Grid.Column="2"
Height="30" Margin="20,20,0,0"
DisplayMemberPath="sDesc"
SelectedValue="{Binding ElementName=label, Path=Content,
TargetNullValue=0, FallbackValue=0, Mode=TwoWay}"
SelectedValuePath="iDX" />
</Grid>
</Window>
EDIT
Relevant documentation: Dependency properties overview
Local value: A local value might be set through the convenience of the property wrapper, which also equates to setting as an attribute or property element in XAML, or by a call to the SetValue method using a property of a specific instance. If you set a local value by using a binding or a static resource, these each act in the precedence as if a local value was set, and bindings or resource references are erased if a new local value is set.
and further down
If you set another local value for a property that originally held a Binding value, you will overwrite the binding entirely, not just the binding's run-time value.
As I understand, there was some kind of bug related to this case, fixed with the introduction of DependencyObject.SetCurrentValue The Control Local Values Bug Solution
public void SetCurrentValue (System.Windows.DependencyProperty dp, object value);
// Sets the value of a dependency property without changing its value source.
It seems to me that Combobox TwoWay binding is still using SetValue, and that's why the binding for (label) gets erased when my (combobox) is used.
To overcome this, I changed the TwoWay binding of (comboBox) to OneWay, and entered the following in the comboBox_DropDownClosed event (showing the currently selected Item), in order to update (label) by code without erasing the existing binding
private void comboBox_DropDownClosed(object sender, System.EventArgs e)
{
Binding binding = BindingOperations.GetBinding(label, Label.ContentProperty);
if (binding != null)
{
ComboItems ComboItem = comboBox.SelectedItem as ComboItems;
int iDX = ComboItem.iDX;
// Set label value without affecting existing binding
label.SetCurrentValue(Label.ContentProperty, iDX);
}
}
With the use of SetCurrentValue, code works now as originally intended by "simulating" the TwoWay mode.
There's nothing strange at all. Data binding is designed to work this way. When you assign a binding to a dependency property, it means you change the local value of this dependency property to a binding expression. And any update provide by the binding source will be the effective value of this dependency property. If the binding is working in one way mode, any update to this dependency property from other then binding source, will overwrite the local value, result in losing the binding. On the other side, becuase two mode is suppose to update the binding source, dependency object will count any non-expression value as effective value, binding will keep working until you replace or clear it.
DependencyObject.GetValue gets the effective value.
DependencyObject.ReadLocalValue gets the local value.
DependencyObject.SetValue sets the local value.
DependencyObject.SetCurrentValue sets the effective value.
DependencyObject.ClearValue clears the local value.
My objective is to include the Label's text in an error message if the content of the Label's TextBox is not valid. During validation, when only the TextBox object is easily obtained, I would like to obtain the reference to the Label object which has had its Target property bound to that TextBox.
In other words, given the source of a binding, I would like to return or retrieve the target of that binding. The WPF BindingOperations.GetBindingExpression() and related methods require that the target object be known already.
In WPF XAML I have this:
<Label Target="{Binding ElementName=RatingTextBox}">_Rating:</Label>
<TextBox Name ="RatingTextBox"/>
In C# code-behind I tried this:
BindingExpression be = RatingTextBox.GetBindingExpression(TextBox.TextProperty);
string format = be.ParentBinding.StringFormat;
However, be.ParentBinding above is null even though my TextBox is definitely bound by the label because the hot key "[Alt]-R" works. Can my TextBox get that Label's text somehow from the C# code-behind?
If I understand correctly, you are looking for a way to automatically bind the Tooltip property of your TextBox to the Content property of whatever Label object the TextBox is a target of.
Unfortunately, to do this most easily would require a mechanism in WPF to, given the source of a binding, identify its target (or targets…a single source can be bound to multiple targets, of course). And as far as I know, no such mechanism exists as such.
However, I can think of at least a couple of different alternatives that should accomplish a similar effect:
When initializing the window, enumerate all the Label objects to find their targets, and update the targets' Tooltip properties accordingly. Either just set them explicitly, or bind the properties to the Label.Content property.
Reverse the direction the Label target is declared. I.e. create an attached property that can be used on the TextBox object, indicating which Label should target it. Then use this attached property to initialize the Tooltip property appropriate (e.g. in the attached property code, bind or set the Tooltip property, or have some other property that is also bound to the attached property and when it changes, handle the binding or setting there).
The motivation for using an attached property in the second option is to allow the label/target relationship to still be declared just once in the XAML (i.e. avoiding redundancy). It's just that the declaration occurs in the target object (i.e. the TextBox) instead of the label object.
Here are a couple of examples showing what I mean…
First option above:
XAML:
<Window x:Class="TestSO32576181BindingGivenSource.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO32576181BindingGivenSource"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label x:Name="label1" Content="_Label:" Target="{Binding ElementName=textBox1}"/>
<TextBox x:Name="textBox1"/>
</StackPanel>
</StackPanel>
</Window>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
InitTooltips(this);
}
private void InitTooltips(FrameworkElement element)
{
foreach (FrameworkElement child in
LogicalTreeHelper.GetChildren(element).OfType<FrameworkElement>())
{
Label label = child as Label;
if (label != null)
{
BindingExpression bindingExpression =
BindingOperations.GetBindingExpression(label, Label.TargetProperty);
if (bindingExpression != null)
{
TextBox textBox =
FindName(bindingExpression.ParentBinding.ElementName) as TextBox;
if (textBox != null)
{
// You could just set the value, as here:
//textBox.ToolTip = label.Content;
// Or actually bind the value, as here:
Binding binding = new Binding();
binding.Source = label;
binding.Path = new PropertyPath("Content");
binding.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(
textBox, TextBox.ToolTipProperty, binding);
}
}
}
InitTooltips(child);
}
}
}
Second option above:
XAML:
<Window x:Class="TestSO32576181BindingGivenSource.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:TestSO32576181BindingGivenSource"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<StackPanel Orientation="Horizontal">
<!-- Note that the Target property is _not_ bound in the Label element -->
<Label x:Name="label1" Content="_Label:"/>
<!-- Instead, it's specified here via the attached property: -->
<TextBox x:Name="textBox1" l:TooltipHelper.TargetOf="{Binding ElementName=label1}"/>
</StackPanel>
</StackPanel>
</Window>
C#:
static class TooltipHelper
{
public static readonly DependencyProperty TargetOfProperty =
DependencyProperty.RegisterAttached("TargetOf", typeof(Label),
typeof(TooltipHelper), new PropertyMetadata(null, _OnTargetOfChanged));
public static void SetTargetOf(FrameworkElement target, Label labelElement)
{
target.SetValue(TargetOfProperty, labelElement);
}
public static Label GetTargetof(FrameworkElement target)
{
return (Label)target.GetValue(TargetOfProperty);
}
private static void _OnTargetOfChanged(
DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Label oldLabel = (Label)e.OldValue,
newLabel = (Label)e.NewValue;
if (oldLabel != null)
{
BindingOperations.ClearBinding(oldLabel, Label.TargetProperty);
BindingOperations.ClearBinding(target, FrameworkElement.ToolTipProperty);
}
if (newLabel != null)
{
Binding binding = new Binding();
binding.Source = newLabel;
binding.Path = new PropertyPath("Content");
binding.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(
target, FrameworkElement.ToolTipProperty, binding);
binding = new Binding();
binding.Source = target;
binding.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(
newLabel, Label.TargetProperty, binding);
}
}
}
Note that in the second option, no new code is required in the window class. Its constructor can just call InitializeComponent() as usual and that's it. All of the code-behind winds up in the TooltipHelper class, which is referenced in the XAML itself.
I created a UserControl that has a ContentControl in it. This ContentControl gets Buttons from the normal .xaml-pages. But depending on some events I need to change this Button's Label or Image but i am getting a NullReferenceException.
UserControl1.xaml
<Grid>
<!-- different Stuff that needs to be around -->
<ContentControl Content="{Binding UserControlContent, ElementName=userContent}"/>
</Grid>
UserControl1.xaml.cs
public static readonly DependencyProperty AppBarContentProperty =
DependencyProperty.Register("UserControlContent", typeof(Grid), typeof(UserControl1), new PropertyMetadata(new Grid()));
public Grid UserControlContent
{
get { return (Grid)GetValue(UserControlContentProperty); }
set { SetValue(UserControlContentProperty, value); }
}
MainPage.xaml
<local:UserControl1>
<local:UserControl1.UserControlContent>
<Grid>
<Controls:RoundButton x:Name="btn1"/>
</Grid>
</local:UserControl1.UserControlContent>
</local:UserControl1>
MainPage.xaml.cs
MainPage()
{
btn1.Label = "new label";
}
As soon as I try this with a button inside of the UserControl it fails. With buttons that stay outside it works.
Is there any deeper binding possible to keep control of these buttons?
The trick is using the mvvm-binding!
The button's values are bound now:
Label="{Binding RoundButtons[3].Label}"
Visibility="{Binding RoundButtons[3].VisibilityState, FallbackValue=Visible}"
This allows me to define default-values and still change them on the fly as I need them to be changed.
Hope someone needs this information ;)
What I have is a well-working C# and XAML code, which does exactly what it is supposed to do, well, almost exactly. I am trying to make my custom, working, DependencyProperty for UserControl - and it is made, well-formed and supposedly working. There are two properties: SumOfApproximationsProperty and SumOfPositionsProperty. These getters and setters simply do not get invoked on certain actions - and this is my problem. They are declared in this UserControl class:
public partial class PresentationCell : UserControl
{
public Label SumOfApproximations;
public Label SumOfPositions;
public PresentationCell()
{
InitializeComponent();
DataContext = this;
this.MinHeight = 40;
this.MinWidth = 40;
SumOfApproximations = this.SumOfApproximation;
SumOfPositions = this.SumOfPosition;
}
public static readonly DependencyProperty SumOfApproximationsProperty =
DependencyProperty.Register("AproximationsProperty", typeof(String),
typeof(PresentationCell), new UIPropertyMetadata(null));
public static readonly DependencyProperty SumOfPositionsProperty =
DependencyProperty.Register("PositionsProperty", typeof(String),
typeof(PresentationCell), new UIPropertyMetadata(null));
public String AproximationsProperty
{
get { return (String)GetValue(SumOfApproximationsProperty); }
set { SetValue(SumOfApproximationsProperty, value); }
}
public String PositionsProperty
{
get { return (String)GetValue(SumOfPositionsProperty); }
set { SetValue(SumOfPositionsProperty, value); }
}
}
As You can see, it is composed of two Labels, that have their own text-setting properties. And here's this UserControl XAML:
// USER CONTROL XAML
<UserControl x:Class="PodstawyModelowaniaISymulacjiRozmytej.Controls.PresentationCell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*">
</ColumnDefinition>
<ColumnDefinition Width="2*">
</ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="1*">
</RowDefinition>
<RowDefinition Height="2*">
</RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Label Name="SumOfApproximation" Content="{Binding Path=AproximationsProperty}">
</Label>
</Grid>
<Grid Grid.Row="1">
</Grid>
</Grid>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="1*">
</RowDefinition>
<RowDefinition Height="2*">
</RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
</Grid>
<Grid Grid.Row="1">
<Label Name="SumOfPosition"></Label>
</Grid>
</Grid>
</Grid>
</UserControl>
This UserControl is going to be used with DataGrid (as it's cells), which is declared below (in XAML):
// MAIN WINDOW DATAGRIG DECLARATION MAINWINDOW.XAML
<Grid Grid.Row="2" Name="DataThree_Grid">
<DataGrid Name="ResultData_DataGrid" HeadersVisibility="Row" Margin="5 5 5 5"></DataGrid>
</Grid>
Here's the code, that prepares and creates a column in this DataGrid, filled with PresentationCell UserControls:
// MAIN WINDOW CREATE COLUMN FOR DATAGRID FUNCTION MAINWINDOW.XAML.CS
private DataGridTemplateColumn CreatePresentationTemplateColumn(Binding positions, Binding aproximations)
{
DataGridTemplateColumn doubleOnlyTextBoxColumn = new DataGridTemplateColumn();
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(PresentationCell));
DataTemplate dataTemplate = new DataTemplate();
factory.SetValue(PresentationCell.SumOfApproximationsProperty, aproximations);
factory.SetValue(PresentationCell.SumOfPositionsProperty, positions);
dataTemplate.VisualTree = factory;
doubleOnlyTextBoxColumn.CellTemplate = dataTemplate;
return doubleOnlyTextBoxColumn;
}
Other code, that can be deemed useful for You to answer this question:
// MAIN WINDOW INITIALIZING BUTTON MAINWINDOW.XAML.CS
private void SubtractionLR_Button_Click(object sender, RoutedEventArgs e)
{
MyData[] table = new MyData[]
{
new MyData
{
Values = new element[2]
{
new element
{
var1 = 7,
var2 = 6
},
new element
{
var1 = 4,
var2 = 1
}
}
},
new MyData
{
Values = new element[2]
{
new element
{
var1 = 67,
var2 = 3
},
new element
{
var1 = 44,
var2 = 1
}
}
}
};
fillPresentationDataGrid(ResultData_DataGrid, table);
}
Now, after all of the code has been described, the problem lingers here. As You can see, I am trying to create Binding object for my column of PresentationCell UserControls. The problem is, that this String in this Binding is rather unknown for me - its specification and so on. As a result, program cannot find data that should be provided to my control (and for its labels) through this binding. The data should come from MyData[] table. Program shows an error about "cannot find Values" etc. and the cells in DataGrid are blank.
// MAIN WINDOW FILLING PRESENTATION GRID FUNCTION MAINWINDOW.XAML.CS
private void fillPresentationDataGrid(DataGrid dataGrid, MyData[] table)
{
dataGrid.AutoGenerateColumns = false;
for (int i = 0; i < table[0].Values.Length; i++)
{
DataGridTemplateColumn col = CreatePresentationTemplateColumn(new Binding("Values[" + i + "].var1"), new Binding("Values[" + i + "].var2"));
dataGrid.Columns.Add(col);
}
dataGrid.ItemsSource = table;
}
EDIT
All I want is to get that MyData[] table content displayed on DataGrid control using my own custom UserControl. When I change that factory.SetValue(PresentationCell.SumOfApproximationsProperty, aproximations); into factory.SetValue(PresentationCell.SumOfApproximationsProperty, "foo");, the DataGrid will display "foo"'s.
EDIT2
Unfortunately, the problem still exists.
In the constructor of PresentationCell you set this.DataContext = this.
By setting DataContext to your control you are breaking the inheritance of this property and thats why setting the bindings in CreatePresentationTemplateColumn wont work.
To fix that you can remove this line and bind the controls by RelativeSource/ElementName or you can set the dataContext to the main grid in PresentationCell instead of the root level
My program contains several instances of this line:
<local:MyClass Data="{Binding}"/>
I.e. the property Data is bound to the data context of the surrounding window. When the value of the window's DataContext changes, the binding is sometimes updated, sometimes not; it depends on the position of <local:MyClass...> in the XAML file.
Here is an example (EDIT: I changed the {Binding} to {Binding Path=DataContext, ElementName=myWindow} to emphasise that the problem is not related to inheritance of DataContext):
XAML code:
<Window x:Class="BindTest.MainWindow"
x:Name="myWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindTest"
Title="Binding Test" Height="101" Width="328">
<Window.Tag>
<local:MyClass x:Name="bindfails" Data="{Binding Path=DataContext, ElementName=myWindow}"/>
</Window.Tag>
<StackPanel Orientation="Horizontal">
<Button Margin="5" Padding="5" Click="SetButtonClicked">Set DataContext</Button>
<Button Margin="5" Padding="5" Click="ReadButtonClicked">Read Bound Property</Button>
<local:MyClass x:Name="bindworks" Data="{Binding Path=DataContext, ElementName=myWindow}"/>
</StackPanel>
</Window>
C# code:
using System.Windows;
namespace BindTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void SetButtonClicked(object sender, RoutedEventArgs e)
{
DataContext = 1234;
}
private void ReadButtonClicked(object sender, RoutedEventArgs e)
{
string txtA = (bindfails.Data == null ? "null" : bindfails.Data.ToString());
string txtB = (bindworks.Data == null ? "null" : bindworks.Data.ToString());
MessageBox.Show(string.Format("bindfails.Data={0}\r\nbindworks.Data={1}", txtA, txtB));
}
}
public class MyClass : FrameworkElement
{
#region Dependency Property "Data"
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(MyClass), new UIPropertyMetadata(null));
#endregion
}
}
First press button "Set DataContext" to change the data context. Then press button "Read Bound Property", which display this message:
bindfails.Data=null
bindworks.Data=1234
Obviously, the data binding was only updated for the MyClass element that is child of the StackPanel; but the data binding was not updated for the MyClass element that is referenced by Window.Tag.
EDIT2: I also have found out that binding works when adding the binding programmatically inside MainWindow's constructor:
Binding binding = new Binding("DataContext") {Source = this};
bindfails.SetBinding(MyClass.DataProperty, binding);
The binding only fails when it is declared in XAML. Furthermore, the problem is not specific to DataContext; it also happens when I use other Window properties, such as Title.
Can anyone explain this behavior and suggest how to allow the use of {Binding} in XAML in both cases?
EDIT3: The above code is not entirely equivalent to the {Binding} markup extension. The 100% equivalent code is:
Binding binding = new Binding("DataContext") {ElementName = "myWindow"};
bindfails.SetBinding(MyClass.DataProperty, binding);
When I use that code, binding also fails (like when binding in XAML) and the following diagnostics message is written to debug output:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=myWindow'.
Obviously, the ElementName property only searches up the visual tree or logical tree, even though this is not documented in the WPF online documentation. Probably, there is no easy way to set such a binding in XAML.
The dataContext is passed only throught the Object Tree. The Property tag is not in the visual tree and will not respond to DataContext changed Event and the binding is not refreshed without this event.
See :
Dependency property identifier field: DataContextProperty from the FrameworlElement
Data context is a concept that allows objects to inherit binding-specifying information from their parents in the object tree
http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.datacontext(v=vs.95).aspx
I suppose it does not work because when you write {Binding}, the context of the data to be bound is inherited from the parent. In case of bindworks, it's a StackPanel, which inherits DataContext from Window, however bindfails.Parent property is null.
I wonder why have you put a control in Window's tag element. If you must keep it declared in Tag node for some reason, you can update its DataContext directly, so just change SetButtonClicked method to:
private void SetButtonClicked(object sender, RoutedEventArgs e)
{
DataContext = 1234;
bindfails.DataContext = DataContext;
}
Another simple method to make it work is just taking bindfails out of Window.Tag and placing it somewhere in the Window, i.e. in the StackPanel.
Next, in the Window's constructor, write this.Tag = bindfails. If you don't want the TextBox to appear in the form, you can set its Visibility to Collapsed (or put it inside a collapsed container control).