WPF: Binding inside resources section - can't access DatatContext - c#

I'm having a binding problem inside the resources section of a DataTemplate:
<Window
...
DataContext="{Binding RelativeSource={RelativeSource Self}}" >
...
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.Resources>
<DataTemplate x:Key="TextTemplate">
<DataTemplate.Resources>
<!-- Binding below doesn't seem to work -->
<local:FreezableProxyElement x:Key="Proxy" Data="{Binding}" />
<local:Message x:Key="Message" Text="{Binding Data, Source={StaticResource Proxy}}" />
</DataTemplate.Resources>
<StackPanel Orientation="Horizontal">
<Label>Text</Label>
<TextBlock Text="{Binding Text, Source={StaticResource Message}}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="IntegerTemplate">
<StackPanel Orientation="Horizontal">
<Label>Integer</Label>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemTemplateSelector>
<local:DataTypeSelector
TextTemplate="{StaticResource TextTemplate}"
IntegerTemplate="{StaticResource IntegerTemplate}"
/>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
Apologies for the length (and the contrived example) - I've tried to be as brief as possible. The important part is the Resources section of the first DataTemplate ("TextTemplate").
Supporting code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public ObservableCollection<object> Items { get; } = new ObservableCollection<object>() { (int)5, "Hello World!" };
}
public class Message : DependencyObject
{
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public static readonly DependencyProperty TextProperty
= DependencyProperty.Register("Text", typeof(string), typeof(Message));
}
public class FreezableProxyElement : Freezable
{
protected override Freezable CreateInstanceCore()
=> new FreezableProxyElement();
public object Data
{
get => (object)GetValue(DataProperty);
set => SetValue(DataProperty, value);
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(FreezableProxyElement));
}
public class DataTypeSelector : DataTemplateSelector
{
public DataTemplate TextTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
switch (item)
{
case string s:
return TextTemplate;
case int i:
return IntegerTemplate;
default:
return null;
}
}
}
I've always understood that in a resources section, a Freezable-derived object could inherit the DataContext from its container (which, in this DataTemplate, should be an item from ItemsControl.ItemsSource and this is verified by the collection's integer "5" being correctly displayed by "IntegerTemplate"). However, the text string is not displayed and the binding to "FreezeableProxyElement.Data" in the "TextTemplate" resources section yields a data error:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:(no path); DataItem=null; target element is 'FreezableProxyElement' (HashCode=12379565); target property is 'Data' (type 'Object')
This is the error I would expect to see if FreezableProxyElement wasn't derived from Freezable. What's the problem here, and is there a good way around it?

I felt a disturbance in the Framework that led me to suspect that the DataTemplate's resources were at one too many removes from reality. I don't know exactly what the problem was (visual tree, logical tree, something or other), but I was able to reproduce it, and the following works for me:
<DataTemplate x:Key="TextTemplate">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<local:FreezableProxyElement
x:Key="Proxy"
Data="{Binding}"
/>
<local:Message
x:Key="Message"
Text="{Binding Data, Source={StaticResource Proxy}}"
/>
</StackPanel.Resources>
<Label>Text</Label>
<Label
Content="{Binding Text, Source={StaticResource Message}}"
/>
</StackPanel>
</DataTemplate>
I still suspect that this is an XY Problem, and that the real solution is to create Message some other way. But without knowing more details I can't give you that answer.
Update
How to do a binding trace:
Text="{Binding Data, Source={StaticResource Proxy}, PresentationTraceSources.TraceLevel=High}"
Don't leave those all over the place, they eat cycles.

Related

WinUI3 XAML Master-Detail View Binding for Different Types

I'm Stuck creating a Master/Detail View in WinUI 3.
screenshot
I Have a treeview as my master list with two different item types
ExplorerItemTypeA and ExplorerItemTypeA each a partial class from the base ExplorerItem
I wish for the detail view to show the correct template for the different type (A&B) so I can bind and edit etc.
The Treeview DataTemplates work fine. Here is the XAML for the details view:
<Page.Resources>
<DataTemplate x:Key="ContentTypeATemplate" x:DataType="local:ExplorerItemTypeA">
<StackPanel Orientation="Horizontal">
<TextBlock Text="A Template"/>
<TextBlock Text="{x:Bind Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ContentTypeBTemplate" x:DataType="local:ExplorerItemTypeB">
<StackPanel Orientation="Horizontal">
<TextBlock Text="B Template"/>
<TextBlock Text="{x:Bind Name}"/>
</StackPanel>
</DataTemplate>
<ExplorerContentTemplateSelector x:Key="ExplorerContentTemplateSelector"
TreeItemTypeATemplate="{StaticResource ContentTypeATemplate}"
TreeItemTypeBTemplate="{StaticResource ContentTypeBTemplate}"/>
</Page.Resources>
If first tried a frame but am now trying a Content presenter for the detail view.
<TreeView x:Name="MasterListView"
Grid.Column="0"
ItemsSource="{x:Bind ViewModel.TemplateItems}"
SelectedItem="{x:Bind ViewModel.SelectedItem,Mode=TwoWay}"
ItemTemplateSelector="{StaticResource ExplorerItemTemplateSelector}"/>
<ContentPresenter
x:Name="DetailContentPresenter"
Grid.Column="1"
Content="{x:Bind MasterListView.SelectedItem, Mode=OneWay}"
ContentTemplateSelector="{StaticResource ExplorerContentTemplateSelector}" />
The Selector is
public class ExplorerContentTemplateSelector : DataTemplateSelector
{
public DataTemplate TreeItemTypeATemplate { get; set; }
public DataTemplate TreeItemTypeBTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
var explorerItem = (ExplorerItem)item;
switch (explorerItem.Type)
{
case ExplorerItem.ExplorerItemType.A:
return TreeItemTypeATemplate;
case ExplorerItem.ExplorerItemType.B:
return TreeItemTypeBTemplate;
default:
Console.WriteLine("Default case");
return TreeItemTypeATemplate;
}
}
}
I feel the answer is obvious and elegant, but despite searching for clues and methods this c# beginner is just not getting anywhere.
I've been stuck on this simple problem for far longer than I care to admit.
Thanks to anyone who is willing to give their time.
Replace the ContentPresenter with a ContentControl in your XAML markup:
<ContentControl
x:Name="DetailContentPresenter"
Grid.Column="1"
Content="{x:Bind MasterListView.SelectedItem, Mode=OneWay}"
ContentTemplateSelector="{StaticResource ExplorerContentTemplateSelector}" />
In your DataTemplateSelector, you should also override the SelectTemplateCore overload that accepts a DependencyObject parameter:
public class ExplorerContentTemplateSelector : DataTemplateSelector
{
public DataTemplate TreeItemTypeATemplate { get; set; }
public DataTemplate TreeItemTypeBTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item) =>
SelectTemplateCore(item, null);
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var explorerItem = item as ExplorerItem;
switch (explorerItem?.Type)
{
case ExplorerItem.ExplorerItemType.A:
return TreeItemTypeATemplate;
case ExplorerItem.ExplorerItemType.B:
return TreeItemTypeBTemplate;
default:
Console.WriteLine("Default case");
return TreeItemTypeATemplate;
}
}
}

How can I bind properties of a view model to properties of items of a collection used as source for an items control

What I am trying to achieve is to bind properties of a ViewModel (mvvm light) object to some self made custom control in a grouped way.
So I created a CustomControl1, with a Title property and an ItemsCollection of my own custom type SomeDataControl.
public class CustomControl1 : Control
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text", typeof(string), typeof(CustomControl1), new PropertyMetadata(default(string)));
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public ObservableCollection<SomeDataControl> _ItemsCollection;
public ObservableCollection<SomeDataControl> ItemsCollection {
get
{
if(_ItemsCollection == null) _ItemsCollection = new ObservableCollection<SomeDataControl>();
return _ItemsCollection;
}
}
}
public class SomeDataControl : DependencyObject
{
public static readonly DependencyProperty LAbelProperty = DependencyProperty.Register(
"LAbel", typeof(string), typeof(SomeDataControl), new PropertyMetadata(default(string)));
public string LAbel
{
get { return (string) GetValue(LAbelProperty); }
set { SetValue(LAbelProperty, value); }
}
public static readonly DependencyProperty DValueProperty = DependencyProperty.Register(
"DValue", typeof(double), typeof(SomeDataControl), new PropertyMetadata(default(double)));
public double DValue
{
get { return (double) GetValue(DValueProperty); }
set { SetValue(DValueProperty, value); }
}
}
I have also added a stlye to render the content of my control into an ItemsControl and bound the values to the appropriate fields like this:
<Style x:Key="ControlStyle" TargetType="local:CustomControl1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<Label Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text}"></Label>
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ItemsCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=LAbel}" />
<Label Content="{Binding Path=DValue}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And I put all this to my view with a view model as DataContext, so I can reach the values.
<Window x:Class="BindingTest.MainWindow" x:Name="thisControl" DataContext="{Binding Source={StaticResource Locator}, Path=VM}">
...
<local:CustomControl1 Text="{Binding Path=DataContext.Text, ElementName=thisControl}" Style="{StaticResource ControlStyle}" >
<local:CustomControl1.ItemsCollection>
<local:SomeDataControl LAbel="Apple" DValue="{Binding Path=DataContext.DVal1, Mode=TwoWay, ElementName=thisControl}">
<local:SomeDataControl LAbel="Peach" DValue="{Binding Path=DataContext.DVal2, Mode=TwoWay, ElementName=thisControl}">
<local:SomeDataControl LAbel="Pear" DValue="{Binding Path=DataContext.DVal3, Mode=TwoWay, ElementName=thisControl}"></local:SomeDataControl>
</local:CustomControl1.ItemsCollection>
</local:CustomControl1>
Everything goes fine, until I want to bind the DVal1,2 and 3 to the specific items. They are all with the default values.
I have been looking for the answer for 3 days already, but I could not find anything that would help. I tried also using DependenyProperty for the collection, or changing the type of it to a simple list, of also Freezable, but nothing helped at all.
I would really like to declare my groups this way in XAML and not putting everything together in my ViewModel to reach the layout.
Any kind of help would be great.
Thank you in advance.
Problem is your bindings
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=LAbel}" />
<Label Content="{Binding Path=DValue}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>``
can not realize while you have Itemsource collection building in progress
<local:CustomControl1.ItemsCollection>
<local:SomeDataControl LAbel="Apple" DValue="{Binding Path=DataContext.DVal1, Mode=TwoWay, ElementName=thisControl}">
<local:SomeDataControl LAbel="Peach" DValue="{Binding Path=DataContext.DVal2, Mode=TwoWay, ElementName=thisControl}">
<local:SomeDataControl LAbel="Pear" DValue="{Binding Path=DataContext.DVal3, Mode=TwoWay, ElementName=thisControl}"></local:SomeDataControl>
</local:CustomControl1.ItemsCollection>
When you create first SomeDataControl ,Style expects an itemsource but its not available till the closing tag is reached.
So, if you dont want in Viewmodel ,create itemsource in Resources section of your window and bind it to your customControl.
<Window.Resources>
<x:Array x:Key="Mycollection" Type="local:SomeDataControl">
<local:SomeDataControl LAbel="Apple"
DValue="{Binding Path=DataContext.DVal1, Mode=TwoWay}"/>
<local:SomeDataControl LAbel="Peach"
DValue="{Binding Path=DataContext.DVal2, Mode=TwoWay}"/>
</x:Array>
</Window.Resources>
and bind to collection
<local:CustomControl1.ItemsCollection ItemsSource={StaticResource Mycollection}/>
Actually I found the answer by using both of the tips and some googling.
The main problem was, that the my SomeDataControl items were not part of the visual tree, therefore they did not get the datacontext of the higher level FrameworkElement.
So I introduced a Binding Proxy thank to this post:
Usage of Binding Proxy
So my XAML looks like this:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</ResourceDictionary>
</Window.Resources>
...
<local:SomeDataControl LAbel="Apple" DValue="{Binding Path=Data.DVal1, Source={StaticResource proxy}}"></local:SomeDataControl>
and the code for the binding proxy is also very good and easy, it worth to re-share it:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object) GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
A little explanation also comes in the linked blog post
The solution to our problem is actually quite simple, and takes advantage of the Freezable class. The primary purpose of this class is to define objects that have a modifiable and a read-only state, but the interesting feature in our case is that Freezable objects can inherit the DataContext even when they’re not in the visual or logical tree.
Thank you all for your support.

defining bindings for elements in a collection in xaml wont work

i have a simple Control with a collection of elements. I want to add elements in xaml and bind to the element.
However when i bind to Bar.Value in xaml it never works. Minimal example:
[ContentProperty("Bars")]
public class FooControl : Control
{
private ObservableCollection<IBar> _bars = new ObservableCollection<IBar>();
public ObservableCollection<IBar> Bars { get { return _bars; } }
}
public interface IBar
{
string Value { get; }
}
public class Bar : DependencyObject, IBar
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(string), typeof(Bar), new PropertyMetadata("<not set>"));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
}
<Window x:Class="WpfTestApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WpfTestApplication"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="200" Width="1000">
<Window.Resources>
<Style TargetType="this:FooControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="this:FooControl">
<ItemsControl ItemsSource="{Binding Bars, RelativeSource={RelativeSource TemplatedParent}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Window.DataContext>
<sys:String>from DataContext</sys:String>
</Window.DataContext>
<Grid>
<this:FooControl>
<this:Bar Value="directly set"/>
<this:Bar Value="{Binding Source=from_binding_source}"/>
<this:Bar Value="{Binding}"/>
<this:Bar Value="{Binding Text, ElementName=SomeTextBlock}"/>
</this:FooControl>
<TextBlock Text="from TextBox" Name="SomeTextBlock" Visibility="Collapsed"/>
</Grid>
</Window>
output
directly set
from_binding_source
"<not set>"
"<not set>"
debug output
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Text; DataItem=null; target element is 'Bar' (HashCode=26568931); target property is 'Value' (type 'String')
Any suggestion how to get it to work?
My current workaround is to define the bindings in code but this is a lot more code and looking at the xaml its not obvious which bindings do exists. (see my answer for workaround code)
dont know if this helps but
[ContentProperty("Bars")]
public class FooControl : Control
{
private ObservableCollection<object> _bars = new ObservableCollection<object>();
public ObservableCollection<object> Bars { get { return _bars; } }
}
xaml
<this:FooControl>
<this:Bar Value="directly set"/>
<this:Bar Value="{Binding Source=from_binding_source}"/>
<TextBlock Text="{Binding}"/>
<TextBlock Text="{Binding Text, ElementName=SomeTextBlock}"/>
</this:FooControl>
you get your desired result
my workaround so far
<this:Bar x:Name="bar3" />
<this:Bar x:Name="bar4" />
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var bar3 = (DependencyObject)FindName("bar3");
BindingOperations.SetBinding(bar3, Bar.ValueProperty, new Binding("DataContext") { Source = this });
var bar4 = (DependencyObject)FindName("bar4");
BindingOperations.SetBinding(bar4, Bar.ValueProperty, new Binding("Text") { Source = FindName("SomeTextBlock") });
}

Show SelectedIndex in WPF Tabcontrol header template

I have 1...n tabcontrols in my application, with the following XAML setup:
<TabControl Name="ordersTabControl" ItemsSource="{Binding CoilItems}">
<TabControl.ItemTemplate>
<DataTemplate DataType="models:Coil">
<StackPanel>
<TextBlock Text="{Binding CoilCode, StringFormat='Coil: {0}'}" />
<TextBlock Text="{Binding ArticleCode, StringFormat='Auftrag: {0}'}" />
<TextBlock Text="{Binding RestWeight, StringFormat='Restgewicht: {0} kg'}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
[...]
</TabControl.ContentTemplate>
</TabControl>
The amount of open tabs changes at runtime. Now I'd like to show an index in each tab (i.e. the first tab shows "Order 1", the second "Order 2" and so on) in addition to the information already in each header.
AFAIK when using DataTemplate I can't access the tab-properties through the code-behind, so is there any way in XAML to bind a textblock inside a tabheader to show the Index of that specific tab in the tabcontrol?
I think it should be possible with RelativeSource and FindAncestors? Alas I couldn't really find any clear tutorial on those settings (and I only started using WPF 2 days ago).
I'm going to give you a solution using attached properties. Check the code:
Attached Properties
public static class IndexAttachedProperty
{
#region TabItemIndex
public static int GetTabItemIndex(DependencyObject obj)
{
return (int) obj.GetValue(TabItemIndexProperty);
}
public static void SetTabItemIndex(DependencyObject obj, int value)
{
obj.SetValue(TabItemIndexProperty, value);
}
// Using a DependencyProperty as the backing store for TabItemIndex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TabItemIndexProperty =
DependencyProperty.RegisterAttached("TabItemIndex", typeof (int), typeof (IndexAttachedProperty),
new PropertyMetadata(-1));
#endregion
#region TrackTabItemIndex
public static bool GetTrackTabItemIndex(DependencyObject obj)
{
return (bool) obj.GetValue(TrackTabItemIndexProperty);
}
public static void SetTrackTabItemIndex(DependencyObject obj, bool value)
{
obj.SetValue(TrackTabItemIndexProperty, value);
}
// Using a DependencyProperty as the backing store for TrackTabItemIndex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TrackTabItemIndexProperty =
DependencyProperty.RegisterAttached("TrackTabItemIndex", typeof (bool), typeof (IndexAttachedProperty),
new PropertyMetadata(false, TrackTabItemIndexOnPropertyChanged));
private static void TrackTabItemIndexOnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tabControl = GetParent(d, p => p is TabControl) as TabControl;
var tabItem = GetParent(d, p => p is TabItem) as TabItem;
if (tabControl == null || tabItem == null)
return;
if (!(bool)e.NewValue)
return;
int index = tabControl.Items.IndexOf(tabItem.DataContext == null ? tabItem : tabItem.DataContext);
SetTabItemIndex(d, index);
}
#endregion
public static DependencyObject GetParent(DependencyObject item, Func<DependencyObject, bool> condition)
{
if (item == null)
return null;
return condition(item) ? item : GetParent(VisualTreeHelper.GetParent(item), condition);
}
}
This code define two attached properties, the first one is to set if an item tracks the the tab item index in wich it is contained. The second one is the index property.
XAML Sample code:
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type WpfApplication3:A}">
<StackPanel x:Name="tabItemRoot" WpfApplication3:IndexAttachedProperty.TrackTabItemIndex ="True">
<TextBlock Text="{Binding Text}"/>
<TextBlock Text="{Binding Path=(WpfApplication3:IndexAttachedProperty.TabItemIndex), ElementName=tabItemRoot}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
The above code is an example of using the attached property. You may adapt to your code easy.
Result:
Hope this code works for you...
If you are not using AlternationCount property for other purposes, you can hack it for an easier solution.
bind AlternationCount like this
<TabControl AlternationCount="{Binding Path=Items.Count, RelativeSource={RelativeSource Self}}">
And then in your ItemTemplate bind the TextBlock or other control you wish to the AlternationIndex like this,
<TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource FindAncestor, AncestorType=TabItem}}" />
With a custom converter plugged into the above binding, you can display anything you want.
Even if you had access to the just the TabItem properties in the code-behind, it wouldn't help as the tab doesn't know it's own index in the TabControl collection. This is true of all ItemsControls and seems annoying, but it makes sense because when can any object ever tell you what its own position is within a collection?
It can be done with IndexOf, however, as long as you have access to both the control's ItemsCollection and the content of the tab. We can do this in a MultiValueConverter, so that it can be done within the DataTemplate.
Converter code:
public class ItemsControlIndexConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ItemCollection itemCollection = (ItemCollection)values[0];
return (itemCollection.IndexOf(values[1]) + 1).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
TabControl XAML:
<TabControl ItemsSource="{Binding CoilItems}">
<TabControl.Resources>
<local:ItemsControlIndexConverter x:Key="IndexConverter"/>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource IndexConverter}" StringFormat="Order {0}" Mode="OneWay">
<Binding RelativeSource="{RelativeSource AncestorType=TabControl}" Path="Items"/> <!-- First converter index is the ItemsCollection -->
<Binding /> <!-- Second index is the content of this tab -->
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!-- Fill in the rest of the header template -->
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>

Binding a property in an ItemsControl ItemTemplate to a DP on the UserControl is not working

A UserControl has 3 Dependency Properties: FormatAvailabilities, Orientation and FullText. FormatAvailabilities is bound to the ItemsSource property of an ItemsControl. Orientation is bound to the Orientation property if the StackPanel which is in the ItemsPanelTemplate within the ItemsControl. FullText is bound to the Visibility property of two TextBlocks inside the DataTemplate of the ItemsControl. I am using two converters to determine which TextBlock to show: a BoolToVisibilityConverter and a BoolToInvertedVisibilityConverter (the latter is an inversion of the former). I copied the Visibility property as-is from the TextBlock (both of them, independently) to the ItemsControl and it works correctly..
It seems that the bindings on the TextBlocks are not working properly because both are always visible. Since they are both binding on the same property but one is inverted, there should never be a possibility for both to be visible at the same time.
I put a breakpoint in my converter and it is never hit, so my guess is that there is an issue with binding from within a repeating control to the outer control in which it is housed.
App.xaml:
<common:BaseApp x:Class="xyz.App" xmlns:converters="clr-namespace:xyz.Converters;assembly=xyz">
<common:BaseApp.RootVisual>
<phone:PhoneApplicationFrame x:Name="RootFrame" Source="/Home.xaml"/>
</common:BaseApp.RootVisual>
<common:BaseApp.Resources>
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<converters:BoolToVisibilityConverter x:Key="BoolToInvertedVisibilityConverter" IfTrue="Collapsed" IfFalse="Visible"/>
</common:BaseApp.Resources>
</common:BaseApp>
UserControl XAML:
<UserControl
x:Name="FormatsControl"
x:Class="xyz.Formats"
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"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480">
<ItemsControl Background="Transparent" ItemsSource="{Binding ElementName=FormatsControl, Path=FormatAvailabilities}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="{Binding ElementName=FormatsControl, Path=Orientation}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding BindsDirectlyToSource=True}" Margin="0,0,10,0" Visibility="{Binding ElementName=FormatsControl, Path=FullText, Converter={StaticResource BoolToVisibilityConverter}}"/>
<TextBlock Text="{Binding Description}" Margin="0,0,10,0" Visibility="{Binding ElementName=FormatsControl, Path=FullText, Converter={StaticResource BoolToInvertedVisibilityConverter}}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
UserControl CS:
namespace xyz
{
public partial class Formats : UserControl
{
public static readonly DependencyProperty FormatAvailabilitiesDependencyProperty = DependencyProperty.Register("FormatAvailabilities", typeof(FormatAvailability[]), typeof(Formats), null);
public static readonly DependencyProperty OrientationDependencyProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(Formats), new PropertyMetadata(Orientation.Horizontal));
public static readonly DependencyProperty FullTextDependencyProperty = DependencyProperty.Register("FullText", typeof(bool), typeof(Formats), null);
public FormatAvailability[] FormatAvailabilities
{
get { return (FormatAvailability[])base.GetValue(Formats.FormatAvailabilitiesDependencyProperty); }
set { base.SetValue(Formats.FormatAvailabilitiesDependencyProperty, value); }
}
public Orientation Orientation
{
get { return (Orientation)base.GetValue(Formats.OrientationDependencyProperty); }
set { base.SetValue(Formats.OrientationDependencyProperty, value); }
}
public bool FullText
{
get { return (bool)base.GetValue(Formats.FullTextDependencyProperty); }
set { base.SetValue(Formats.FullTextDependencyProperty, value); }
}
public Formats()
{
InitializeComponent();
}
}
}
I must be over looking something...thanks!
There is an issue with naming UserControls in Silverlight 3 as described by this blog post, which is also present in the Windows Phone 7 version of Silverlight. Effectively, if you give the UserControl a name in the XAML where it is used (i.e. it's parent), then that overrides the name given in the UserControl's own XAML file.
I ran into a similar problem, instead of binding to the elementname I changed the binding to this
Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}
And that works just fine.
Looks like you are missing the OnPropertyChanged handler.
Here is one of my dependency properties. Note the changed handler.
public ObservableCollection<ObjWithDesc> ItemsSource
{
get
{
return (ObservableCollection<ObjWithDesc>)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
"ItemsSource",
typeof(ObservableCollection<ObjWithDesc>),
typeof(HorizontalListBox),
new PropertyMetadata(OnItemsSourcePropertyChanged)
);
static void OnItemsSourcePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((HorizontalListBox) obj).OnItemsSourcePropertyChanged(e);
}
private void OnItemsSourcePropertyChanged(DependencyPropertyChangedEventArgs e)
{
ObservableCollection<ObjWithDesc> objWithDescList = (ObservableCollection<ObjWithDesc>)e.NewValue;
MainListBox.ItemsSource = objWithDescList;
}

Categories

Resources