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
Related
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
I would like to propagate DataContext upwards from a dynamically created DataTemplate hosted withing a ContentControl such as following one:
var myFactory = new FrameworkElementFactory(controlTypeToDisplay);//= typeof(MyControl)
var dctxBinding = new Binding
{
Path = new PropertyPath("DataContext.Dctx"),
Mode = BindingMode.OneWayToSource,
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ContentControl), 1)
};
myFactory.SetBinding(FrameworkElement.DataContextProperty, dctxBinding);
return new DataTemplate() { VisualTree = myFactory };
However the result of such binding is null even though the DataContext is being set within MyControl's constructor. DataContext of MyControl definitely is not set null further down the road, constructor is invoked before setter of Dctx. How can I fix the binding so that MyControl's DataContext and Dctx property are always in sync?
Full minimal example of the issue (should display two "FooBar" TextBlocks if working correctly):
//MyControl.xaml
<Grid>
<TextBlock Text="{Binding}"/>
</Grid>
//MyControl.xaml.cs
public MyControl()
{
InitializeComponent();
DataContext = "FooBar";
this.DataContextChanged += MyControl_DataContextChanged;
}
private void MyControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("An unexpected change");
}
//MainWindow.xaml
<StackPanel>
<ContentControl ContentTemplate="{Binding DataTemplate}"/>
<TextBlock Text="{Binding Dctx, TargetNullValue='<null>'}" />
</StackPanel>
//MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
private Type controlTypeToDisplay = typeof(MyControl);
public DataTemplate DataTemplate
{ get {/*see first listing*/ } }
private object _dctx;
public object Dctx
{
get { return _dctx; }
set { _dctx = value; RaisePropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string caller = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
}
The DataContext of the root element in the ContentTemplate of a ContentControl is the ContentControl's Content. Bind the Content property to your DataContext:
<ContentControl Content="{Binding Dctx}" ContentTemplate="{Binding DataTemplate}"/>
You must also set the DataContext of the ContentControl itself (or the parent window) somewhere, and add some visual element to the DataTemplate:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public DataTemplate DataTemplate
{
get {
var myFactory = new FrameworkElementFactory(typeof(TextBlock));
myFactory.SetBinding(TextBlock.TextProperty, new Binding(".")); //bind to the DataContext
return new DataTemplate() { VisualTree = myFactory };
}
}
private object _dctx = new object(); //set the Dxtc property somewhere
public object Dctx
{
get { return _dctx; }
set { _dctx = value; RaisePropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string caller = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
}
I'm trying to create my own, very simple, Usercontrol in WPF. It's basically just a Combobox, with some additional Logic in it.
I tried to create my own Depdency-Property using this Tutorial: http://www.codeproject.com/Articles/140620/WPF-Tutorial-Dependency-Property
This is working fine so far, but if the property changes, I'd like to reflect this on the Combobox in the User-Control as well. It seems like I can't bind the subcontrol directly to my new Dependency-Project.
My code is looking like this at the moment:
public partial class ClassSelector : UserControl
{
public static readonly DependencyProperty CurrentValueProperty =
DependencyProperty.Register("CurrentValue", typeof(ClassType),
typeof(ClassSelector), new FrameworkPropertyMetadata());
public ClassType CurrentValue
{
get
{
return (ClassType)this.GetValue(CurrentValueProperty);
}
set
{
this.SetValue(CurrentValueProperty, value);
}
}
public ClassSelector()
{
this.DataContext = this;
InitializeComponent();
cmbClassType.ItemsSource = Enum.GetValues(typeof(ClassType));
}
}
Setting the value of the dependy-property or the Combobox seems weirds to me.
I tried to bind it direclty in the xaml via:
<Grid>
<ComboBox x:Name="cmbClassType" SelectedItem="{Binding Path=CurrentValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="cmbClassType_SelectionChanged" />
</Grid>
I tried to map the Dependicy-Project changed Event with the combobox and visa versa, but this leads to very strange code, since the combobox change would have to change the property-value and the property-value the combobox.
I'm quite sure there has to be a possibility to bind a DependencyProperty to a subcontrol, but I can't find a way to make this work.
Thanks in advance for all advices guys and have a nice weekend
Matthias
Edith says: The calling Window needs to bind the Object to the Grid, not to the Window, so for example:
grdMain.DataContext = new DeckSearch();
is working fine, meanwhile
this.DataContext = new DeckSearch();
This behavior is ONLY at my custom control, all other controls worked perfectly fine with the DataContext on the Window itself.
Okay so here I fixed your code and it is working at my end
UserControlCodeBehind
public partial class ClassSelector : UserControl
{
public static readonly DependencyProperty CurrentValueProperty = DependencyProperty.Register("CurrentValue", typeof(ClassType),
typeof(ClassSelector), new FrameworkPropertyMetadata()
{
DefaultValue = ClassType.Type1,
BindsTwoWayByDefault = true,
PropertyChangedCallback = CurrentValueChanged,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
private static void CurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = (ClassSelector)d;
obj.cmbClassType.SelectedValue = e.NewValue;
}
public ClassType CurrentValue
{
get
{
return (ClassType)this.GetValue(CurrentValueProperty);
}
set
{
this.SetValue(CurrentValueProperty, value);
}
}
public ClassSelector()
{
InitializeComponent();
cmbClassType.ItemsSource = Enum.GetValues(typeof(ClassType));
cmbClassType.SelectedValue = CurrentValue;
}
}
The Xaml part of the UserControl
<Grid>
<ComboBox x:Name="cmbClassType" SelectedValue="{Binding Path=CurrentValue}"/>
</Grid>
Please check if it is working at your end. I have not added any extra code checking for thread safety and all.
EDIT
In my solution I do get my Class Property notification when the CurrentValue changes.
Below is my sample MainWindow Code.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Task.Run(() =>
{
Thread.Sleep(1000);
Dispatcher.InvokeAsync(() =>
{
customCombobox.CurrentValue = ClassType.Type3;//Updating the UserControl DP
});
Thread.Sleep(2000);
this.CurrentValue = ClassType.Type2;//Updating my local Property
});
}
private ClassType _currentValue;
public ClassType CurrentValue
{
get { return _currentValue; }
set
{
_currentValue = value;
Debug.WriteLine("Value Changed to " + value.ToString());
RaisePropertyChanged("CurrentValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propName)
{
var pc = PropertyChanged;
if (pc != null)
pc(this, new PropertyChangedEventArgs(propName));
}
}
And my MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="MainWindow" Height="250" Width="525">
<local:ClassSelector x:Name="customCombobox" Height="25" CurrentValue="{Binding CurrentValue}"/>
</Window>
I have recently discovered indexed properties. This looks like the perfect solution to the scenario in which the data I am working with would best be expressed in a collection, yet still needs to be implemented as a property that can be used in XAML databinding. I started with just a test of creating indexed properties, and I had no problems there, but I just don't seem to be able to get the binding to work.
Can anyone point out where I'm going wrong?
Here is the test class with a nested class to create the indexed property:
public class TestListProperty
{
public readonly ListProperty ListData;
//---------------------------
public class ListProperty : INotifyPropertyChanged
{
private List<string> m_data;
internal ListProperty()
{
m_data = new List<string>();
}
public string this[int index]
{
get
{
if ( m_data.Count > index )
{
return m_data[index];
}
else
{
return "Element not set for " + index.ToString();
}
}
set
{
if ( m_data.Count > index )
{
m_data[index] = value;
}
else
{
m_data.Insert( index, value );
}
OnPropertyChanged( "Item[]" );
Console.WriteLine( value );
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged( string propertyName )
{
PropertyChangedEventHandler handler = PropertyChanged;
if ( handler != null ) handler( this, new PropertyChangedEventArgs( propertyName ) );
}
}
//---------------------------
public TestListProperty()
{
ListData = new ListProperty();
}
}
Here is the XAML:
<Window x:Class="TestIndexedProperties.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Width="200" Grid.Row="0" Text="{Binding Path=ListData[0], Mode=TwoWay}" />
<TextBox Width="200" Grid.Row="1" Text="{Binding Path=ListData[1], Mode=TwoWay}" />
<TextBox Width="200" Grid.Row="2" Text="{Binding Path=ListData[2], Mode=TwoWay}" />
</Grid>
</Window>
And here is the Window code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TestListProperty test = new TestListProperty();
this.DataContext = test;
test.ListData[0] = "ABC";
test.ListData[1] = "Pleeez 2 wurk?";
test.ListData[2] = "Oh well";
}
}
Thanks for any and all help!
There's no mechanism in your code to indicate to the front-end when the list has changed, i.e. ListProperty is implementing INotifyPropertyChanged instead of INotifyCollectionChanged. The easiest fix would be to change m_data to type ObservableCollection and bind your XAML controls to that instead, although that probably defeats the purpose of what you're trying to do in the first place. The "proper" way is to subscribe to the CollectionChanged event and propagate the messages through:
public class TestListProperty
{
public ListProperty ListData { get; private set; }
//---------------------------
public class ListProperty : INotifyCollectionChanged
{
private ObservableCollection<string> m_data;
internal ListProperty()
{
m_data = new ObservableCollection<string>();
m_data.CollectionChanged += (s, e) =>
{
if (CollectionChanged != null)
CollectionChanged(s, e);
};
}
public string this[int index]
{
get
{
if (m_data.Count > index)
{
return m_data[index];
}
else
{
return "Element not set for " + index.ToString();
}
}
set
{
if (m_data.Count > index)
{
m_data[index] = value;
}
else
{
m_data.Insert(index, value);
}
Console.WriteLine(value);
}
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
//---------------------------
public TestListProperty()
{
ListData = new ListProperty();
}
}
When using databinding in WPF, the target dependency object gets updated when it is notified that the source has changed through the INotifyPropertyChanged interface.
For example:
<TextBlock Text="{Binding Path=SomeField}"/>
The text field will change to correctly reflect the value of SomeField whenever PropertyChanged(this, new PropertyChangedEventArgs("SomeField")) is called from the source.
What if I use a complex path like the following:
<TextBlock Text="{Binding Path=SomeObjField.AnotherField}"/>
Will the text field get updated for PropertyChanged(this, new PropertyChangedEventArgs("SomeObjField")) on the source?
What about PropertyChanged(this, new PropertyChangedEventArgs("AnotherField")) on the intermediate object (the object contained within the SomeObjField)?
Source objects and fields are NOT dependency objects or properties! Assume that the property/classes are implemented something like the following:
public class Data : INotifyPropertyChanged
{
// INotifyPropertyChanged implementation...
public string SomeField
{
get { return val; }
set
{
val = value;
// fire PropertyChanged()
}
}
public SubData SomeObjField
{
get { return val; }
set
{
val = value;
// fire PropertyChanged()
}
}
}
public class SubData : INotifyPropertyChanged
{
// INotifyPropertyChanged implementation...
public string AnotherField
{
get { return val; }
set
{
val = value;
// fire PropertyChanged()
}
}
}
After further investigation, it appears that when any part of the complex path sends a change notification the binding is updated. Thus, if the source object OR the intermediate object is changed the binding will be updated.
I built a test project like Jared's:
<StackPanel Name="m_panel">
<TextBox IsReadOnly="True" Text="{Binding Path=SomeObjField.AnotherField }" />
<TextBox x:Name="field1"/>
<Button Click="Button1_Click">Edit Root Object</Button>
<TextBox x:Name="field2"/>
<Button Click="Button2_Click">Edit Sub Object</Button>
</StackPanel>
And the code behind:
public Window1()
{
InitializeComponent();
m_panel.DataContext = new Data();
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
Data d = m_panel.DataContext as Data;
d.SomeObjField = new SubData(field1.Text);
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
Data d = m_panel.DataContext as Data;
d.SomeObjField.AnotherField = field2.Text;
}
I am using the basic data implementation that I provided in the question.
I'm not 100% sure what your asking with the PropertyChanged part of the question. But if the properties involved are all DependencyProperty backed properties then this should work as expected. I drew up the following example
Window1.xaml
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel Name="m_panel">
<TextBlock Text="{Binding Path=SomeField}" />
<TextBlock Text="{Binding Path=SomeField.AnotherField }" />
<Button Click="Button_Click">Update Root Object</Button>
<Button Click="Button_Click_1">Update Another Field</Button>
</StackPanel>
</Window>
Window1.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
m_panel.DataContext = new Class1();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
((Class1)m_panel.DataContext).SomeField = new Class2();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
((Class1)m_panel.DataContext).SomeField.AnotherField = "Updated field";
}
}
And the classes
public class Class1 : DependencyObject
{
public static DependencyProperty SomeFieldProperty = DependencyProperty.Register(
"SomeField",
typeof(Class2),
typeof(Class1));
public Class2 SomeField
{
get { return (Class2)GetValue(SomeFieldProperty); }
set { SetValue(SomeFieldProperty, value); }
}
public Class1()
{
SomeField = new Class2();
}
}
public class Class2 : DependencyObject
{
public static DependencyProperty AnotherFieldProperty = DependencyProperty.Register(
"AnotherField",
typeof(string),
typeof(Class2));
public string AnotherField
{
get { return (string)GetValue(AnotherFieldProperty); }
set { SetValue(AnotherFieldProperty, value); }
}
public Class2()
{
AnotherField = "Default Value";
}
}