User Control Child elements sharing Value across multiple instances - c#

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

Related

Organize Items in DropDownButton?

I've a collection of items inside an ObservableCollection, each item have a specific nation name (that's only a string). This is my collection:
private ObservableCollection<League> _leagues = new ObservableCollection<League>();
public ObservableCollection<League> Leagues
{
get
{
return _leagues;
}
set
{
_leagues = value;
OnPropertyChanged();
}
}
the League model have only a Name and a NationName properties.
The Xaml looks like this:
<Controls:DropDownButton Content="Leagues" x:Name="LeagueMenu"
ItemsSource="{Binding Leagues}"
ItemTemplate="{StaticResource CombinedTemplate}" >
<Controls:DropDownButton.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding NationName}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</Controls:DropDownButton.GroupStyle>
</Controls:DropDownButton>
but I doesn't get any header for the NationName property, the items inside the DropDown are organized without header but as list, so without organization.
I'm trying to get this predisposition.
What am I doing wrong?
Preliminaries
Grouping items in an ItemsControl in WPF (which DropDownButton derives from) is fairly simple, and is accomplished in two steps. First you need to set up the items source by tweaking an ICollectionView associated with the source collection. Then you need to populate the ItemsControl.GroupStyle collection with at least one GroupStyle item - otherwise the items are presented in a plain (non-grouped) manner.
Diagnosis
The main issue you're facing is getting the drop-down to present the items in a grouped manner. Unfortunately, unlike setting up the items source, it is not something that is easily accomplished in case of the DropDownButton control. The reason for that stems from the way the control (or, more precisely, its template) is designed - the drop-down is presented inside a ContextMenu attached to a Button which is part of the template (see MahApps.Metro source code). Now ContextMenu also derives from ItemsControl, and most of its properties are bound to corresponding properties of the templated DropDownButton. That is however not the case for its GroupStyle property, because it's a read-only non-dependency property, and cannot be bound or event styled. That means that even if you add items to DropDownButton.GroupStyle collection, the ContextMenu.GroupStyle collection remains empty, hence the items are presented in non-grouped manner.
Solution (workaround)
The most reliable, yet most cumbersome solution would be to re-template the control and add GroupStyle items directly to the ContextMenu.GroupStyle collection. But I can offer you a much more concise workaround.
First of all, let's deal with the first step - setting up the items source. The easiest way (in my opinion) is to use CollectionViewSource in XAML. In your case it would boil down to something along these lines:
<mah:DropDownButton>
<mah:DropDownButton.Resources>
<CollectionViewSource x:Key="LeaguesViewSource" Source="{Binding Leagues}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="NationName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</mah:DropDownButton.Resources>
<mah:DropDownButton.ItemsSource>
<Binding Source="{StaticResource LeaguesViewSource}" />
</mah:DropDownButton.ItemsSource>
</mah:DropDownButton>
Now for the main part - the idea is that we'll create a helper class that will contain one attached dependency property that will assign an owner DropDownButton control to the ContextMenu responsible for presenting its items. Upon changing the owner we'll observe its DropDownButton.GroupStyle collection and use ContextMenu.GroupStyleSelector to feed the ContextMenu with items coming from its owner's collection. Here's the code:
public static class DropDownButtonHelper
{
public static readonly DependencyProperty OwnerProperty =
DependencyProperty.RegisterAttached("Owner", typeof(DropDownButton), typeof(DropDownButtonHelper), new PropertyMetadata(OwnerChanged));
public static DropDownButton GetOwner(ContextMenu menu)
{
return (DropDownButton)menu.GetValue(OwnerProperty);
}
public static void SetOwner(ContextMenu menu, DropDownButton value)
{
menu.SetValue(OwnerProperty, value);
}
private static void OwnerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var menu = (ContextMenu)d;
if (e.OldValue != null)
//unsubscribe from the old owner
((DropDownButton)e.OldValue).GroupStyle.CollectionChanged -= menu.OwnerGroupStyleChanged;
if (e.NewValue != null)
{
var button = (DropDownButton)e.NewValue;
//subscribe to new owner
button.GroupStyle.CollectionChanged += menu.OwnerGroupStyleChanged;
menu.GroupStyleSelector = button.SelectGroupStyle;
}
else
menu.GroupStyleSelector = null;
}
private static void OwnerGroupStyleChanged(this ContextMenu menu, object sender, NotifyCollectionChangedEventArgs e)
{
//this method is invoked whenever owners GroupStyle collection is modified,
//so we need to update the GroupStyleSelector
menu.GroupStyleSelector = GetOwner(menu).SelectGroupStyle;
}
private static GroupStyle SelectGroupStyle(this DropDownButton button, CollectionViewGroup group, int level)
{
//we select a proper GroupStyle from the owner's GroupStyle collection
var index = Math.Min(level, button.GroupStyle.Count - 1);
return button.GroupStyle.Any() ? button.GroupStyle[index] : null;
}
}
In order to complete the second step we need to bind the Owner property for the ContextMenu (we'll use DropDownButton.MenuStyle to do that) and add some GroupStyle items to the DropDownButton:
<mah:DropDownButton>
<mah:DropDownButton.MenuStyle>
<Style TargetType="ContextMenu" BasedOn="{StaticResource {x:Type ContextMenu}}">
<Setter Property="local:DropDownButtonHelper.Owner" Value="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
</mah:DropDownButton.MenuStyle>
<mah:DropDownButton.GroupStyle>
<GroupStyle />
</mah:DropDownButton.GroupStyle>
</mah:DropDownButton>
This I think should be enough to achieve your goal.
If you check out the other post you've linked to, the answer has it all - in particular you need to bind to a CollectionView, rather than directly to the collection. Then you can set up grouping on the CollectionView.
So, in your case, define the property:
public ICollectionView LeaguesView { get; private set; }
and then after you've created your Leagues Collection, attach the View to your collection, and while you're at it set up the grouping on the view:
LeaguesView = (ListCollectionView)CollectionViewSource.GetDefaultView(Leagues);
LeaguesView.GroupDesriptions.Add(new PropertyGroupDescription("NationName"));
Then, bind your DropDownButton ItemSource to LeaguesView, and change your HeaderTemplate to bind to "Name" - which is the the name of the group:
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
You can also use the ItemCount property in there if you want to show how many items there are in the group.

C# WPF Datacontext Binding dynamic Elements

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.

How do I access a Label object, given the TextBox bound its Content property?

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.

Binding collection to ComboBox in DataGrid

I have two questions about binding ComboBox to lists objects, when the ComboBox are implemented in DataGrid. But they are so interrelated, that I think two threads are not constructive.
I have a fistful of classes, and I want show their data in a xceed DataGrid. My DataContext is set to ViewModelClass. It has a list of class X objects:
public class ViewModelClass
{
public IList<X> ListX { get; set; }
}
The class X looks something like this. It has a property Id, and a list list of class Y objects.
The list should be my ItemsSource for the ComboBoxes (in DataGrid).
public class X
{
public int Id { get; set; }
// this should be my ItemsSource for the ComboBoxes
public IList<Y> ListY { get; set; }
}
The class Y and Z look something like this. They are some kinds of very simple classes:
public class Y
{
public Z PropZ { get; set; }
}
public class Z
{
public string Name { get; set; }
}
My XAML-Code looks something like this.
<Grid.Resources>
<xcdg:DataGridCollectionViewSource x:Key="ListX" AutoCreateItemProperties="False"
Source="{Binding Path=ListX,
UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}" />
</Grid.Resources>
<p:DataGrid AutoCreateColumns="False"
ItemsSource="{Binding Source={StaticResource ListX},
UpdateSourceTrigger=PropertyChanged}">
<xcdg:Column Title="Id" FieldName="Id" />
<xcdg:Column Title="Functions" **FieldName="ListY"**>
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<ComboBox DisplayMemberPath="PropZ.Name"
**ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type xcdg:DataGridControl}}, Path=ItemsSource.ListY**}" SelectedValuePath="Funktion.FunktionId" />
</DataTemplate>
</xcdg:Column.CellContentTemplate>
</xcdg:Column>
Now I dont know, how can I bind the ItemsSource of the ComboBox, so that I can read the list values of ListY in my X class?
Then I dont know what is in fact my FieldName for the Functions column?
I entered ListY, because it represents the property (IList<>) in my X class. But I think it is probably not right.
Thanks a lot for your help!
To answer your first question - try this
<ComboBox ItemsSource="{Binding Path=DataContext.ListY,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
For your seconds question I am not too sure (it is up to you) but the field name would probably be SelectedFunction or something along those lines
Let's break down your problem into bite sized pieces. You have a ListX collection that is data bound to a DataGrid.ItemsSource property:
<DataGrid ItemsSource="{Binding ListX}" ... />
One thing to note about your code at this stage is that it is pointless setting the Binding.UpdateSourceTrigger property to PropertyChanged on the ItemsSource property. From the linked page:
Bindings that are TwoWay or OneWayToSource listen for changes in the target property and propagate them back to the source. This is known as updating the source. Usually, these updates happen whenever the target property changes. This is fine for check boxes and other simple controls, but it is usually not appropriate for text fields. Updating after every keystroke can diminish performance and it denies the user the usual opportunity to backspace and fix typing errors before committing to the new value. Therefore, the default UpdateSourceTrigger value of the Text property is LostFocus and not PropertyChanged.
You really should know what the code does before you use it.
So anyway, back to your problem... we have a data bound DataGrid and one of its columns has a ComboBox in it. I'm not really sure why you're not using the DataGridComboBoxColumn Class or equivalent, but no matter. Now, you need to understand something about all collection controls:
If a collection of type A is data bound to the ItemsSource property of a collection control, then each item of the collection control will be an instance of type A. This means that the DataContext of each item will be set to that instance of type A. This means that we have access to all of the properties defined in class A from within any DataTemplate that defines what each item should look like.
That means that you have direct access to the ListY property of the X class from within the DataTemplate that defines what your items should look like. Therefore, you should be able to do this:
<DataTemplate>
<ComboBox DisplayMemberPath="PropZ.Name" ItemsSource="{Binding ListY}"
SelectedValuePath="Funktion.FunktionId" />
</DataTemplate>
I can't confirm whether the SelectedValuePath that you set will work, because you didn't mention it anywhere, but if your class Y doesn't have a property named Funktion in it, then it will not work. You'll also have to explain your second problem better, as I didn't really understand it.
I have found a solution, but even that has not proven to be productive. Because the allocation of cell.Content to the comboBox.ItemsSource shows no effect in my View :-(
In XAML, I have the following code
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<p:XDataGridComboBox
DataRow="{Binding RelativeSource={RelativeSource AncestorType={x:Type xcdg:DataRow}}}"
ItemsFieldName="Functions" />
</DataTemplate>
</xcdg:Column.CellContentTemplate>
I have written a custom control in which I explicitly set the data source for each ComboBox:
static XDataGridComboBox()
{
DataRowProperty = DependencyProperty.RegisterAttached(
"DataRow",
typeof(DataRow),
typeof(XDataGridComboBox),
new FrameworkPropertyMetadata(OnChangeDataRow));
ItemsFieldNameProperty = DependencyProperty.RegisterAttached(
"ItemsFieldName",
typeof(string),
typeof(XDataGridComboBox),
new FrameworkPropertyMetadata(OnChangeItemsFieldName));
}
private static void OnChangeDataRow(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var comboBox = d as XDataGridComboBox;
if (comboBox == null)
{
return;
}
var cell =
(from DataCell c in comboBox.DataRow.Cells where c.FieldName == comboBox.ItemsFieldName select c)
.FirstOrDefault();
if (cell == null)
{
return;
}
comboBox.ItemsSource = cell.Content as IEnumerable;
}
The data that I need are available, but the view does not show it. I do not know what I have not considered.

Binding sometimes fails, depending on position of {Binding} in XAML file

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).

Categories