I have a stackpanel that is filled with dynamic editors. The values inside the editors (TextBoxes, DatePickers, etc) are based on a item from a listbox. I create these editors based on the class and its properties bound to the listbox.
The "rendered" XAML code would something like this:
<StackPanel Name="LeftEditorStack">
<StackPanel Name="OuterPanelFirstname">
<RadioButton Name="FirstnameEnabled"></RadioButton>
<!--or any other possible FrameWorkElement-->
<TextBox></TextBox>
</StackPanel>
</StackPanel>
The TextBox has a databinding that is bound in code behind.
editorBinding = new Binding();
editorBinding.Path = new PropertyPath(String.Format("DataContext.{0}", properties[i].Name));
editorBinding.RelativeSource = new RelativeSource() { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(StackPanel), AncestorLevel = 1 };
//editorAttribute is a custom Attribute that contains some information about the type of the Editor, BindingProperty, Position, Size and other stuff
editor.SetBinding(editorAttribute.BindingProperty, editorBinding);
This means the Datacontext of the LeftEditorStack is the real source for the binding of the TextBox. This works fine, but when I change the DataContext of the LeftEditorStack the TextBox does not get the update. The Update is occurs inside of a SelectionChanged Event:
private void lb_left_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.LeftEditorStack.DataContext = null;
this.LeftEditorStack.DataContext = this.lb_left.SelectedItem;
}
How can I get the TextBox to change its value when the DataContext is updated? I can not use UpdateTarget from the BindingExpression because the usercontrol that contains my editors has no direct access to the dynamic editors.
Also setting the BindingMode or the UpdateSourceTrigger did not change this behaviour.
Update
As Grx70 pointed out my AncestorLevel was wrong. After I set the AncestorLevel to 2 it works fine.
Related
I've got an issue where if an item is selected in a list I want it to update my items in my grid. The binding is done by:
<ScrollViewer Grid.Row="1">
<ItemsControl x:Name="RightGridItemsControl" ItemsSource="{Binding News}" ItemTemplate="{StaticResource RightGridTemplate}"/>
</ScrollViewer>
When an item, e.g. Planet is selected, I want to update the ItemsSource binding to a new list. This is specified in my DataModel.
How can I update this programmatically? I've tried something like this, but it requires a DependencyObject and can't find out what it means. This also looks like WPF rather than UWP.
`var myBinding = new Binding
{
Source = Planets,
Mode = BindingMode.OneWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
BindingOperations.SetBinding(new , ItemsControl.ItemsSourceProperty, myBinding);`
What should I put as the first item for the contstructor for 'SetBinding'?
You can set the Binding like this:
BindingOperations.SetBinding(
RightGridItemsControl, ItemsControl.ItemsSourceProperty, myBinding);
or like this:
RightGridItemsControl.SetBinding(ItemsControl.ItemsSourceProperty, myBinding);
Note also that currently there is no property path present in your Binding. If there is a News property as in your XAML, the Binding should probably look like shown below, without Mode = BindingMode.OneWay, which is the default, and without UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged, which has no effect in a one-way binding.
var myBinding = new Binding
{
Source = Planets,
Path = new PropertyPath("News")
};
I have made a User Control, FontSelector, that groups together a ComboBox for FontFamily Selection and three ToggleButtons for Bold, Italics, Underline options. I am having an issue with the ComboBox's SelectedItem property affecting all instances of that User Control within the same Window. For example, changing the ComboBox selection on one, will automatically change the other. For Clarity. I don't want this behavior. I am very surprised that a User Control is implicitly affecting another User Control.
XAML
<Grid x:Name="Grid" Background="White" DataContext="{Binding RelativeSource={RelativeSource AncestorType=local:FontSelector}}">
<ComboBox x:Name="comboBox" Width="135"
SelectedItem="{Binding Path=SelectedFontFamily}" Style="{StaticResource FontChooserComboBoxStyle}"
ItemsSource="{Binding Source={StaticResource SystemFontFamilies}}"/>
</Grid>
Code Behind
The CLR Property that the ComboBox's SelectedItem is Bound to. Code shown here is in the User Control Code Behind File, not a ViewModel.
private FontFamily _SelectedFontFamily;
public FontFamily SelectedFontFamily
{
get
{
return _SelectedFontFamily;
}
set
{
if (_SelectedFontFamily != value)
{
_SelectedFontFamily = value;
// Modify External Dependency Property Value.
if (value != SelectedFont.FontFamily)
{
SelectedFont = new Typeface(value, GetStyle(), GetWeight(), FontStretches.Normal);
}
// Notify.
RaisePropertyChanged(nameof(SelectedFontFamily));
}
}
}
The Dependency Property that updates it's value based on the Value of the ComboBox's SelectedItem Property. It effectively packages the FontFamily value into a Typeface Object.
public Typeface SelectedFont
{
get { return (Typeface)GetValue(SelectedFontProperty); }
set { SetValue(SelectedFontProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedFont. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedFontProperty =
DependencyProperty.Register("SelectedFont", typeof(Typeface), typeof(FontSelector),
new FrameworkPropertyMetadata(new Typeface("Arial"), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnSelectedFontPropertyChanged)));
private static void OnSelectedFontPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as FontSelector;
var newFont = e.NewValue as Typeface;
if (newFont != null)
{
instance.SelectedFontFamily = newFont.FontFamily;
}
}
EDIT
I think I may have figured out what is going on. I can replicate it by Binding the ItemsSource to the Following Collection View Source.
<CollectionViewSource x:Key="SystemFontFamilies" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Source"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
You can then replicate the behavior by placing 2 ComboBoxes and Binding both of them to the CollectionViewSource. They will now, seemingly implicitly track each others SelectedItem. Even without Any Data Binding outside of ItemsSource. It would seem that the CollectionViewSource is somehow playing a part in what the SelectedItem is.
I'd make it a bit different. I'll introduce this solution using only a String, not FontFamily or FontWeight, since I have no VS here right now. (In order to have it working, please change the list of FontFamilies to a list of strings to bind them.)
Your selector UserControl:
- your xaml is ok (but you won't need the x:Name)
- the CodeBehind of the UserControl (later: UC) should change, we will solve it with binding. You should have a DependencyProperty, lets' call it SelectedFontFamily, which will represent the selected string from the ComboBox:
public string SelectedFontFamily
{
get { return (string)GetValue(SelectedFontFamilyProperty); }
set { SetValue(SelectedFontFamilyProperty, value); }
}
public static readonly DependencyProperty SelectedFontFamilyProperty = DependencyProperty.Register("SelectedFontFamily", typeof(string), typeof(YourUC), new PropertyMetadata(string.Empty));
The Window, which contains the UC:
- You should include the namespace of the UC's folder in the opening tag of the window, eg:
<Window
...
xmlns:view="clr-namespace:YourProjectName.Views.UserControls">
- the window's DataContext should have a property with public set option (feel free to implement INotifyPropertyChange on it):
public string FontFamily {get; set;}
- in the Window's xaml you would use the UC this way:
<view:YourUC SelectedFontFamily="{Binding FontFamily, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
It's a two-way binding. You'll find the selected string as the value of the FontFamily property every time you change the SelectedItem.
Edit: you will need View Model class for the Window which is using the UserControl. Create it, make it implement the INotifyPropertyChanged interface, and set it as DataContext for your consumer window. WPF is not like WF, you can find more about it if you Google up "WPF MVVM" or something like that.
Found the problem. I was binding to a CollectionViewSource defined in Application Resources. Until now I was unaware that Binding to a CollectionViewSource will also affect the SelectedItem. The SelectedItem Data gets stored as part of the CollectionViewSource. Setting the IsSynchronizedWithCurrentItem property to False on the ComboBox solved the issue.
Here is an existing answer I have now Found.
Thanks
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 have a dataBinding ListBox in a Pivot section.
In another Pivot section i have a form to create new items for the ListBox.
When i add a new item to the ListBox i need to acces to the new ListBoxItem for find a TextBox control and modify the Text value, but
ListBoxItem lbItem = allItemsListBox.ItemContainerGenerator.ContainerFromIndex(itemIndex) as ListBoxItem;
Always returns null.
The problem seems to be that the ListBox is not visible, so the new ListBoxItem is virtual.
How can i resolve that?
Thanks.
Why not use data binding with DataTemplates instead of trying to modify the controls directly?
for example:
Code:
// Data binding class
public class Data
{
// Implement INofifyPropertyChanged
public string Text { get; set; }
}
// Code to bind it to Pivot
ObservableCollection<Data> list = new ObservableCollection<Data>();
// populate list
Pivot1.ItemsSource = list;
XAML:
<Pivot Name="Pivot1">
<Pivot.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding Text}" />
</DataTemplate>
</Pivot.ItemTemplate>
</Pivot>
Now, to change Text of a specific TextBlock, all you need to do is change a value of associated Data object to it from the list.
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).