I created a custom user control, which displays a string (Var1) in different colors, according to the file ColorCode1:
<local:MyFormattedTextControl Text="{Binding SelectedItem.Var1, ElementName=myListView, UpdateSourceTrigger=PropertyChanged}" PartFlags="{Binding SelectedItem.ColorCode1, ElementName=myListView}" />
This works perfectly except for one detail: I cannot use TextWrapping="Wrap"
Can someone tell me how to update the User Control to be able to use TextWrappin?
The User Control looks like that:
<UserControl
<UserControl.Resources>
<local:PartColorValueConverter x:Key="partColorValueConv" />
</UserControl.Resources>
<Grid>
<StackPanel x:Name="myStackPanel" Orientation="Horizontal"/>
</Grid>
</UserControl>
Code Behind for this User Control:
public partial class MyFormattedTextControl : UserControl, INotifyPropertyChanged {
//Constructor
public MyFormattedTextControl() {
InitializeComponent();
myStackPanel.DataContext = this;
}
//Creating Dependency Properties for Text and ColorCode
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public IEnumerable<int> PartFlags {
get { return (int[])GetValue(PartFlagsProperty); }
set { SetValue(PartFlagsProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(MyFormattedTextControl), new PropertyMetadata(null, OnTextPropertyChanged));
public static readonly DependencyProperty PartFlagsProperty = DependencyProperty.Register("PartFlags", typeof(int[]), typeof(MyFormattedTextControl), new PropertyMetadata(new int[] { }));
//OnPropertyChange
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
//Get Object
var ctrl = (MyFormattedTextControl)d;
var t = (string)e.NewValue;
ctrl.myStackPanel.Children.Clear();
//Fill Array with single characters
for (int i = 0; i < t.Length; i++) {
//Create TextBox
TextBlock tb = new TextBlock();
tb.Text = t.Substring(i, 1);
Binding b = new Binding("PartFlags");
PartColorValueConverter conv = new PartColorValueConverter();
b.Converter = conv;
b.ConverterParameter = i;
tb.SetBinding(ForegroundProperty, b);
//Add Text Box to StackPanel
ctrl.myStackPanel.Children.Add(tb);
}
}
private void OnPropertyChanged([CallerMemberName] string callerMember = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(callerMember));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Your problem is because you use many textblocks rather than one.
A Textblock contains Inlines. These can be various things, like newline. The most common is a run.
Hence you can do:
<TextBlock TextWrapping="Wrap">
<Run Foreground="Red">A</Run>
<Run Foreground="Blue">b</Run>
<Run Foreground="Green">c</Run>
</TextBlock>
Clear inlines and add a run per letter with the appropriate text and foreground.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.textblock.inlines?view=netframework-4.8
Related
I have a ListView in UWP which displays a list of custom controls CustomControl. Reading around I have seen that other users have face similar issues and their solution mostly revolved around setting the DataContext of their controls, but I cannot understand how I can do that in my example. In order to dynamically update the view I used DependencyProperties in my model which is the following:
public class DataObject : DependencyObject
{
public string Name
{
get { return (string)GetValue(nameProperty); }
set { SetValue(nameProperty, value); }
}
// Using a DependencyProperty as the backing store for name. This enables animation, styling, binding, etc...
public static readonly DependencyProperty nameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(DataObject), new PropertyMetadata("Name"));
}
Then in my main page I implemented the following logic to change the Name of my third element:
public sealed partial class MainPage : Page
{
private readonly ObservableCollection<DataObject> dataList;
public MainPage()
{
this.InitializeComponent();
this.dataList = new ObservableCollection<DataObject>();
for (int i = 0; i < 5; i++)
{
DataObject dataObject = new DataObject();
dataObject.Name = "Item " + i.ToString();
this.dataList.Add(dataObject);
}
DataListView.ItemsSource = dataList;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var obj = dataList.ElementAt(2);
obj.Name = "Hello!";
}
}
The XAML for the main page is the following:
<Page
x:Class="ListViewTest.MainPage"
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:controls="using:ListViewTest.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<ListView Name="DataListView">
<ListView.ItemTemplate>
<DataTemplate>
<controls:CustomControl DisplayName="{Binding Name}"></controls:CustomControl>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Button" HorizontalAlignment="Center" Height="92" Width="238"
Click="Button_Click"/>
</Grid>
</Page>
The custom control CustomControl is this:
namespace ListViewTest.Controls
{
public sealed partial class CustomControl : UserControl
{
public string DisplayName
{
get { return (string)GetValue(DisplayNameProperty); }
set {
SetValue(DisplayNameProperty, value);
DisplayText.Text = value;
}
}
// Using a DependencyProperty as the backing store for DisplayName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayNameProperty =
DependencyProperty.Register("DisplayName", typeof(string), typeof(CustomControl), new PropertyMetadata("DisplayText"));
public CustomControl()
{
this.InitializeComponent();
}
}
Its structure is very simple:
<Grid>
<Button Name="ClickButton" Content="Button" Margin="171,165,0,0" VerticalAlignment="Top"/>
<TextBlock Name="DisplayText" HorizontalAlignment="Center" TextWrapping="Wrap" VerticalAlignment="Center"/>
</Grid>
The problem is that when I click the button nothing happens and I am struggling to understand why.
DataObject shouldn't inherit from DependencyObject. It should be defined as a CLR object that implements INotifyPropertyChanged:
public class DataObject : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Also, the setter of the CLR wrapper for the dependency property in CustomControls should only set the value of the dependency property. You could set the value of the TextBlock using a PropertyChangedCallback:
public sealed partial class CustomControl : UserControl
{
public string DisplayName
{
get { return (string)GetValue(DisplayNameProperty); }
set { SetValue(DisplayNameProperty, value); }
}
public static readonly DependencyProperty DisplayNameProperty =
DependencyProperty.Register(nameof(DisplayName), typeof(string), typeof(CustomControl),
new PropertyMetadata("DisplayText", new PropertyChangedCallback(OnChanged)));
private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomControl customControl = (CustomControl)d;
d.DisplayText = e.NewValue as string;
}
public CustomControl()
{
this.InitializeComponent();
}
}
When I try to use a custom UserControl in a ListView, it fails and only empty blocks are displayed (the following TextBlock works though). While the customControl outside the ListView works pretty well. What's the problem?
MainWindow.xaml
<Grid>
<StackPanel>
<controls:CustomControl x:Name="customControl" CustomText="Test"/>
<ListView x:Name="listView">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<controls:CustomControl CustomObject="{Binding}"/>
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
InitializeMyComponent();
}
public System.Collections.ObjectModel.ObservableCollection<CustomClass> CustomCollection { get; set; }
private void InitializeMyComponent()
{
this.CustomCollection = new System.Collections.ObjectModel.ObservableCollection<CustomClass>();
this.CustomCollection.Add(new CustomClass() { Number = 1, Text = "a" });
this.CustomCollection.Add(new CustomClass() { Number = 2, Text = "b" });
this.CustomCollection.Add(new CustomClass() { Number = 3, Text = "c" });
this.listView.ItemsSource = this.CustomCollection;
this.customControl.Custom = new CustomClass() { Number = 0, Text = "customControl" };
}
}
CustomControl.xaml
<Grid>
<StackPanel>
<TextBlock>
<Run x:Name="numberRun" Text="{Binding CustomObject.Number}"/>
<Run x:Name="textRun" Text="{Binding CustomObject.Text}"/>
<Run Text="{Binding CustomText}"/>
</TextBlock>
</StackPanel>
</Grid>
CustomControl.cs
public partial class CustomControl : UserControl
{
public static readonly DependencyProperty CustomObjectProperty;
public static readonly DependencyProperty CustomTextProperty;
static CustomControl()
{
CustomObjectProperty = DependencyProperty.Register("CustomObject", typeof(CustomClass), typeof(CustomControl), new PropertyMetadata(default(CustomClass), OnCustomObjectPropertyChanged));
CustomTextProperty = DependencyProperty.Register("CustomText", typeof(string), typeof(CustomControl), new PropertyMetadata(string.Empty, OnCustomTextPropertyChanged));
}
public CustomControl()
{
InitializeComponent();
this.DataContext = this;
}
public CustomClass CustomObject
{
get
{
return (CustomClass)(this.GetValue(CustomObjectProperty));
}
set
{
this.SetValue(CustomObjectProperty, value);
}
}
public string CustomText
{
get
{
return (string)(this.GetValue(CustomTextProperty));
}
set
{
this.SetValue(CustomTextProperty, value);
}
}
private static void OnCustomObjectPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
private static void OnCustomTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
}
CustomClass.cs
public class CustomClass : INotifyPropertyChanged
{
private int number;
private string text;
public CustomClass()
{
this.number = new int();
this.text = string.Empty;
}
public int Number
{
get
{
return this.number;
}
set
{
this.number = value;
this.OnPropertyChanged();
}
}
public string Text
{
get
{
return this.text;
}
set
{
this.text = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = null)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Setting a UserControl's DataContext to the UserControl instance like
this.DataContext = this;
effectively prevents bindings to an "external", inherited DataContext, as expected in
<controls:CustomControl CustomObject="{Binding}"/>
As a general rule, never set a UserControl's DataContext explicitly. Remove the above line from the UserControl's constructor, and write the bindings in the UserControl's XAML with RelativeSource:
<Run x:Name="numberRun" Text="{Binding CustomObject.Number,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
My WPF Application code generates panels on function call defined in .cs file. There is ItemControl used in code to generates these Panels . I want to Name Textbox defined in this ItemControl and to use this in code. I named it as textEdit1 and used it in code but code generated error that textEdit1 doesn't exist. Can anyone solve my problem? Here Code is:
XAML File:
<dxlc:ScrollBox>
<ItemsControl Name="lstPanels">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="vertical">
<Grid>
<dxe:TextEdit Height="165" Text="{Binding Text,
Mode=TwoWay}" x:Name="textEdit1"/>
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</dxlc:ScrollBox>
.CS FILE
public partial class Window1 : Window
{
string valuu;
public Window1()
{
InitializeComponent();
addPanel("Header1");
addPanel("Header2");
addPanel("Header3");
lstPanels.ItemsSource = panels;
}
public ObservableCollection<MyPanel> panels = new ObservableCollection<MyPanel>();
public void addPanel(string buttonId)
{
MyPanel p = new MyPanel { Id = buttonId};
panels.Add(p);
functionb(p);
}
public void functionb(MyPanel obj)
{
valuu = obj.Text;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
foreach (var f in panels.ToList())
{
MessageBox.Show( f.Id + " *** " + f.Text);
}
}
}
public class MyPanel : INotifyPropertyChanged
{
private string _id;
private string _text;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged( String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I see that you are using some 3rd party libraries for your TextBox and ScrollBox. If you provide me with the names of the libraries, I could have a look at them as the functionality might be different from what WPF has out-of-the-box.
As for now you have 3 options (I am giving examples for standard TextBox and ItemsControl):
I) You do not have to access the textbox at all.
An easy way around it is described here: StackOverflow post
II) Handling events and references to TextBoxes in the code behind
Add a Loaded event to your TextBox:
<TextBox x:Name="txtText" Width="300" Height="100" Loaded="txtText_Loaded" />
Add a field to your MyPanel class to hold a reference to a TextBox:
public class MyPanel
{
public string Text { get; set; }
public TextBox TextBox { get; set; }
/* the rest ... */
}
Add a counter to your window, next to a list with panels:
protected ObservableCollection<MyPanel> panels = new ObservableCollection<MyPanel>();
private int counter = 0;
Handle the Load event of the TextBox:
private void txtText_Loaded(object sender, RoutedEventArgs e)
{
panels[counter].TextBox = (TextBox)sender;
counter++;
}
If you want to access a particular TextBox, do it this way:
MessageBox.Show(panels[i].TextBox.Text);
III) Add additional bindings for FontSize:
Add a FontSize property to your MyPanel class:
private double _fontSize = 10;
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
Bind just added property to the TextBox in your ItemsControl:
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}"
FontSize="{Binding FontSize, Mode=OneWay}" />
Add a slider to the template and bind it to the same property:
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
This way if you change the value on a slider, it will change the value in your MyPanel object bound to the panel. This in turn will change the font size of the textbox.
My whole code I tested it on looks like that:
<ItemsControl x:Name="lstItems" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBox x:Name="txtText" Width="300" Height="100" Text="{Binding Text;, Mode=TwoWay}" FontSize="{Binding FontSize, Mode=OneWay}" />
<Slider Minimum="10" Maximum="30" Value="{Binding FontSize, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And code behind:
public partial class MainWindow : Window
{
protected ObservableCollection<MyPanel> texts = new ObservableCollection<MyPanel>();
public MainWindow()
{
InitializeComponent();
texts.Add(new MyPanel() { Text = "Test 1" });
texts.Add(new MyPanel() { Text = "Test 2" });
lstItems.ItemsSource = texts;
}
}
public class MyPanel : INotifyPropertyChanged
{
private string _id;
private string _text;
private double _fontSize = 10;
public string Id
{
get { return _id; }
set
{
if (value != _id)
{
_id = value;
NotifyPropertyChanged();
}
}
}
public string Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
NotifyPropertyChanged();
}
}
}
public double FontSize
{
get { return _fontSize; }
set
{
if (value != _fontSize)
{
_fontSize = value;
NotifyPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I personally would go with the last solution.
But again, let me know what libraries you are using, and I will have look at them when I have some time. Good luck.
textEdit1 is part of a template that will be instantiated multiple times, so there will be multiple instances of textEdit1. It wouldn't make sense to generate a field for textEdit1 in the class, because it could only refer to one instance the TextEdit control...
we have a wpf-window with some textboxes and a datagrid.
the textboxes descripe a parent (class a) object and the datagrid lists a collection of "childs" (class b => not derived from class a).
the childs can inherit values from the parent.
for example if the parent (class a) has a property Foo then the child object (class b) has a property Nullable which can either override the value of the parent or inherit the value of the parent.
now the datagrid should display the value in gray (if it is inherited) or in black (if the user overrides the value in the grid cell).
Unfortunatly Binding to InheritedText doesnt work. Does someone have any idea?
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<UserControls:InheritedTextBoxControl
Text="{Binding Path=?}"
InheritedText="{Binding Path=?}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Thanks in advance
Tobi
--UPDATE--
xaml of InheritedTextBoxControl:
<UserControl x:Class="Com.QueoMedia.CO2Simulationstool.WPF.Utils.UserControls.InheritedTextBoxControl"
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"
Width="Auto"
Height="Auto"
Name="cnt">
<Grid x:Name="LayoutRoot"
Background="White">
<TextBox TextChanged="TextBoxTextChanged"></TextBox>
<TextBlock Name="inheritedText"
IsHitTestVisible="False"
Margin="4,0"
VerticalAlignment="Center"
Opacity="0.5"
FontStyle="Italic"></TextBlock>
</Grid>
CodeBehind:
public partial class InheritedTextBoxControl : UserControl {
private bool _isInherited;
public static readonly DependencyProperty InheritedTextProperty = DependencyProperty.Register("InheritedText", typeof(String), typeof(InheritedTextBoxControl), new PropertyMetadata(""));
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(InheritedTextBoxControl), new PropertyMetadata(default(string)));
public InheritedTextBoxControl() {
InitializeComponent();
}
public string InheritedText {
get { return (string)GetValue(InheritedTextProperty); }
set {
SetValue(InheritedTextProperty, value);
inheritedText.Text = value;
}
}
private bool IsInherited {
get { return _isInherited; }
set {
_isInherited = value;
if (value) {
inheritedText.Opacity = 0.5;
} else {
inheritedText.Opacity = 0;
}
}
}
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private void TextBoxTextChanged(object sender, TextChangedEventArgs e) {
if (((TextBox)sender).Text.Length > 0) {
IsInherited = false;
} else {
IsInherited = true;
}
Text = ((TextBox)sender).Text;
}
}
The problem is the setter of your InheritedText property. WPF won't call this setter when the property is set from XAML. See Checklist for Defining a Dependency Property, section Implementing the "Wrapper" for details.
You will have to update inheritedText.Text in a PropertyChangedCallback like this:
public static readonly DependencyProperty InheritedTextProperty =
DependencyProperty.Register(
"InheritedText", typeof(string), typeof(InheritedTextBoxControl),
new PropertyMetadata(string.Empty, InheritedTextChanged));
private static void InheritedTextChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((InheritedTextBoxControl)d).inheritedText.Text = (string)e.NewValue;
}
public string InheritedText
{
get { return (string)GetValue(InheritedTextProperty); }
set { SetValue(InheritedTextProperty, value); } // only call SetValue here
}
If someone is interested in the solution:
we did it using a CellTemplate containing a CustomControl name MaskedTextbox that has three properties (MaskedText, Text, IsMaskTextVisible) and a CellEditingTemplate to override the data.
The values are bound to an InheritableValueViewModel.
Tobi
I am trying to do follow DataBinding
Property -> DependencyProperty -> Property
But i have trouble.
For example,
We have simple class with two properties implements INotifyPropertyChanged:
public class MyClass : INotifyPropertyChanged
{
private string _num1;
public string Num1
{
get { return _num1; }
set
{
_num1 = value;
OnPropertyChanged("Num1");
}
}
private string _num2;
public string Num2
{
get { return _num2; }
set
{
_num2 = value;
OnPropertyChanged("Num2");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(e));
}
}
And TextBlock declared in xaml:
<TextBlock Name="tb" FontSize="20" Foreground="Red" Text="qwerqwerwqer" />
Now lets trying to bind Num1 to tb.Text:
private MyClass _myClass = new MyClass();
public MainWindow()
{
InitializeComponent();
Binding binding1 = new Binding("Num1")
{
Source = _myClass,
Mode = BindingMode.OneWay
};
Binding binding2 = new Binding("Num2")
{
Source = _myClass,
Mode = BindingMode.TwoWay
};
tb.SetBinding(TextBlock.TextProperty, binding1);
//tb.SetBinding(TextBlock.TextProperty, binding2);
var timer = new Timer(500) {Enabled = true,};
timer.Elapsed += (sender, args) => _myClass.Num1 += "a";
timer.Start();
}
It works well. But if we uncomment this string
tb.SetBinding(TextBlock.TextProperty, binding2);
then TextBlock display nothing. DataBinding doesn't work! How can i to do what i want?
The problem is that the SetBinding call clears out any previous bindings. So when you set a binding to Num2, you are clearing out the binding to Num1. This happens because a dependency property binding cannot have multiple sources- how would it know which one to use? (Of course, this ignores the usage of a MultiBinding, but that's not going to help you in this scenario).
The way you can do this is to make MyClass a DependencyObject and Num1 and Num2 dependency properties. Then you can bind Num2 to the Text property of the TextBox, and Num2 will be updated whenever the text receives an update from Num1.
A picture is worth a thousand words- what you're trying to do is shown on the left. What you need to do is shown on the right:
alt text http://img339.imageshack.us/img339/448/twosources.png
Decided to try this out to ensure my logic was sound, and indeed it works, but there are some tricks. For starters, here is the new MyClass code:
public class MyClass : FrameworkElement
{
public static readonly DependencyProperty Num1Property =
DependencyProperty.Register("Num1", typeof(string), typeof(MyClass));
public static readonly DependencyProperty Num2Property =
DependencyProperty.Register("Num2", typeof(string), typeof(MyClass));
public string Num1
{
get { return (string)GetValue(Num1Property); }
set { SetValue(Num1Property, value); }
}
public string Num2
{
get { return (string)GetValue(Num2Property); }
set { SetValue(Num2Property, value); }
}
}
Nothing scary here, just replaced your INotifyPropertyChanged with DependencyProperty. Now let's check out the window code-behind:
public partial class DataBindingChain : Window
{
public MyClass MyClass
{
get;
set;
}
public DataBindingChain()
{
MyClass = new MyClass();
InitializeComponent();
Binding binding1 = new Binding("Num1")
{
Source = MyClass,
Mode = BindingMode.OneWay
};
Binding binding2 = new Binding("Text")
{
Source = tb,
Mode = BindingMode.OneWay
};
tb.SetBinding(TextBlock.TextProperty, binding1);
MyClass.SetBinding(MyClass.Num2Property, binding2);
var timer = new Timer(500) { Enabled = true, };
timer.Elapsed += (sender, args) => Dispatcher.Invoke(UpdateAction, MyClass);
timer.Start();
}
Action<MyClass> UpdateAction = (myClass) => { myClass.Num1 += "a"; };
}
This is where the magic happens: we set up two bindings. The first binds the TextBlock.Text to Num1, the second binds Num2 to the TextBlock.Text. Now we have a scenario like the right side of the picture I showed you- a data-binding chain. The other magic is that we cannot update the Num1 property on a different thread from the one it was created on- that would create a cross-thread exception. To bypass this, we simply invoke an update onto the UI thread using the Dispatcher.
Finally, the XAML used for demonstration:
<Window x:Class="TestWpfApplication.DataBindingChain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBindingChain" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Name="tb" Grid.Row="0" FontSize="20" Foreground="Red"/>
<TextBlock Name="tb2" Grid.Row="1" FontSize="20" Foreground="Blue" Text="{Binding MyClass.Num2}"/>
</Grid>
And voila! The finished product:
alt text http://img163.imageshack.us/img163/6114/victorynf.png