UserControl ItemsControl Binding - wpf

I am developing a UserControl which contains an ItemsControl of TextBoxes. I have a caption class which is used to control the location/Text of the TextBlock
In the Window XAML
<local:UserControl1>
<local:UserControl1.Captions>
<local:Caption Foreground="Black" Size="10" Text="{Binding Path=SomeText}" X="100" Y ="100"></local:Caption>
</local:UserControl1.Captions>
</local:UserControl1>
The ItemsControl is a canvas with the dataTemplate being a TextBox
<ItemsControl ItemsSource="{Binding Path=Captions}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Beige" Width="{Binding Path=Width}" Height="{Binding Path=Height}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Text}" Foreground="{Binding Path=Foreground}" FontSize="{Binding Path=Size}" ></TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top" Value="{Binding Path=Y,PresentationTraceSources.TraceLevel=High}" />
<Setter Property="Canvas.Left" Value="{Binding Path=X,PresentationTraceSources.TraceLevel=High}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
In the UserControl is an ObservableCollection of "Caption" which is derived from Framework element
public class Caption :FrameworkElement
{
/// <summary>
/// The actual text to display
/// </summary>
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// The font size
/// </summary>
public int Size
{
get { return (int)GetValue(SizeProperty); }
set { SetValue(SizeProperty, value); }
}
/// <summary>
/// The text foreground color
/// </summary>
public Brush Foreground
{
get { return (Brush)GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
/// <summary>
/// The Top location of the text
/// </summary>
public double Y
{
get { return (double)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
/// <summary>
/// The left location of the text
/// </summary>
public double X
{
get { return (double)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
public override string ToString()
{
return string.Format("Caption:{0}//{1}.{2}", this.X, this.Y, this.Text);
}
private static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string),
typeof(Caption),
new PropertyMetadata(
OnPropertyChanged));
private static readonly DependencyProperty SizeProperty = DependencyProperty.Register("Size", typeof(int),
typeof(Caption),
new PropertyMetadata(
OnPropertyChanged));
private static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register("Foreground",
typeof(Brush),
typeof(Caption),
new PropertyMetadata
(OnPropertyChanged));
private static readonly DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double),
typeof(Caption),
new PropertyMetadata(
OnPropertyChanged));
private static readonly DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double),
typeof(Caption),
new PropertyMetadata(
OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var caption = (Caption)d;
}
}
Each time a Caption is added to the collection it is added to the logical tree
void Captions_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if(e.Action == NotifyCollectionChangedAction.Add)
{
foreach(Caption c in e.NewItems)
{
AddLogicalChild(c);
c.DataContext = this.DataContext;
}
}
}
The problem is that the binding for the Canvas.Top and Canvas.Left are not working
The debug produces the following
System.Windows.Data Warning: 54 : Created BindingExpression
(hash=14626603) for Binding (hash=8360729)
System.Windows.Data Warning: 56 : Path: 'X'
System.Windows.Data Warning: 58 : BindingExpression (hash=14626603):
Default mode resolved to OneWay
System.Windows.Data Warning: 59 : BindingExpression (hash=14626603):
Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 60 : BindingExpression (hash=14626603):
Attach to WpfApplicationQuery.Caption.Left (hash=33822626)
System.Windows.Data Warning: 65 : BindingExpression (hash=14626603):
Resolving source
System.Windows.Data Warning: 68 : BindingExpression (hash=14626603):
Found data context element: Caption (hash=33822626) (OK)
System.Windows.Data Warning: 76 : BindingExpression (hash=14626603):
Activate with root item
System.Windows.Data Warning: 104 : BindingExpression (hash=14626603):
Item at level 0 is null - no accessor
System.Windows.Data Warning: 78 : BindingExpression (hash=14626603):
TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 86 : BindingExpression (hash=14626603):
TransferValue - using fallback/default value 'NaN'
System.Windows.Data Warning: 87 : BindingExpression (hash=14626603):
TransferValue - using final value 'NaN'
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector
are ignored for items already of the ItemsControl's container type;
Type='Caption'
So it seems to binding incorrectly, and I don't know what the last error means
Also, previously, I had Caption derived from DependencyObject, and everything worked (.i.e things got displayed), except Databinding. Hence the switch to framworkElement, as described somewhere on stackoverflow ( and a blog post which I now can't find)
Edit 2012-02-14
Changed the first XAML sample code
Edit 2012-02-14
Caption derives from FrameworkElement so that it can participate in databinding as described here
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/aff23943-5483-40b2-816b-4ce687bc6bf8/
and here
http://kentb.blogspot.com/2008_10_01_archive.html
If Caption implements INotifyPropertyChanged
<local:UserControl1>
<local:UserControl1.Captions>
<local:Caption Foreground="Black" Size="10" Text="Hello" X="100" Y ="100"></local:Caption>
<local:Caption Foreground="Black" Size="10" Text="{Binding Path=Title}" X="100" Y ="100"></local:Caption>
</local:UserControl1.Captions>
</local:UserControl1>
The first line works. The second does'nt, this error is traced.
Cannot find governing FrameworkElement or FrameworkContentElement for target element
However, if Caption is derived from FrameworkElement, it now provides it's own ItemTemplate
So the binding error relating to ItemTemplate/ Selector is produced.

Related

ValidationRule and DependencyObject

I need to send Binding values to a ValidationRule. I am using a DependencyObject, but the values are always null.
Here is my DependencyObject
public class MyDependencyObject : DependencyObject
{
public string BindingValue
{
get { return (string)GetValue(BindingValueProperty); }
set { SetValue(BindingValueProperty, value); }
}
public static readonly DependencyProperty BindingValueProperty =
DependencyProperty.Register("BindingValue", typeof(string), typeof(MyDependencyObject), new UIPropertyMetadata(null));
}
Here is my ValidationRule:
public class MyTextBoxValidationRule : ValidationRule
{
private MyDependencyObject _TxtVal;
public MyDependencyObject TxtVal
{
get { return _TxtVal; }
set { _TxtVal = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
//Validation Logic
}
}
Here is the XAML:
<TextBox >
<TextBox.Text>
<Binding Path="DataUserTypes"
NotifyOnValidationError="True"
ValidatesOnDataErrors="True"
Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged"
NotifyOnSourceUpdated="True"
NotifyOnTargetUpdated="True"
Delay="100">
<Binding.ValidationRules>
<local:MyTextBoxValidationRule ValidatesOnTargetUpdated="True" >
<local:MyTextBoxValidationRule.TxtVal >
<local:MyDependencyObject
TxtVal="{Binding Path=ValidateValue}" />
</local:MyTextBoxValidationRule.TxtVal>
</local:MyTextBoxValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
When I step through Validate in MyTextBoxValidationRule TxtVal.BindingValue is always null. I have no idea why
edit:
I changed DisplayValue to DataUserTypes as there seems to be some confusion.
<Binding Path="DataUserTypes"
That is the binding for the Text value of the textbox. I need to validate DataUserTypes based on the property ValidateValue. I am trying to do that with the DependencyObject TxtVal.
I also fixed a copy and paste type. There was a field called TextValID that should have been TxtVal. Sorry about that.
<local:MyTextBoxValidationRule ValidatesOnTargetUpdated="True" >
<local:MyTextBoxValidationRule.TxtVal >
<local:MyDependencyObject
BindingValue="{Binding ValidateValue, PresentationTraceSources.TraceLevel=High}"
/>
</local:MyTextBoxValidationRule.TxtVal>
</local:MyTextBoxValidationRule>
PresentationTraceSources.TraceLevel=High produces the following trace output in the VS Output pane:
System.Windows.Data Warning: 56 : Created BindingExpression (hash=24230272) for Binding (hash=28316044)
System.Windows.Data Warning: 58 : Path: 'ValidateValue'
System.Windows.Data Warning: 60 : BindingExpression (hash=24230272): Default mode resolved to OneWay
System.Windows.Data Warning: 61 : BindingExpression (hash=24230272): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 62 : BindingExpression (hash=24230272): Attach to Lines.MyDependencyObject.BindingValue (hash=37689768)
System.Windows.Data Warning: 64 : BindingExpression (hash=24230272): Use Framework mentor <null>
System.Windows.Data Warning: 67 : BindingExpression (hash=24230272): Resolving source
System.Windows.Data Warning: 69 : BindingExpression (hash=24230272): Framework mentor not found
System.Windows.Data Warning: 65 : BindingExpression (hash=24230272): Resolve source deferred
'Lines.exe' (CLR v4.0.30319: Lines.exe): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\PresentationFramework.Aero\v4.0_4.0.0.0__31bf3856ad364e35\PresentationFramework.Aero.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
System.Windows.Data Warning: 67 : BindingExpression (hash=24230272): Resolving source
System.Windows.Data Warning: 69 : BindingExpression (hash=24230272): Framework mentor not found
Long story short, that instance of local:MyDependencyObject has no place to inherit a DataContext from, because its parent isn't in the visual tree.
You could use a BindingProxy to work around that, but what are you binding to? ValidateValue must be a property of something; but of what? If it's a property of the parent viewmodel, this will do it:
<TextBox.Resources>
<local:BindingProxy
x:Key="ViewmodelProxy"
Data="{Binding}"
/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="DataUserTypes"
NotifyOnValidationError="True"
ValidatesOnDataErrors="True"
Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged"
NotifyOnSourceUpdated="True"
NotifyOnTargetUpdated="True"
Delay="100">
<Binding.ValidationRules>
<local:MyTextBoxValidationRule ValidatesOnTargetUpdated="True" >
<local:MyTextBoxValidationRule.TxtVal>
<local:MyDependencyObject
BindingValue="{Binding Data.ValidateValue, Source={StaticResource ViewmodelProxy}}"
/>
</local:MyTextBoxValidationRule.TxtVal>
</local:MyTextBoxValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
Binding proxy:
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));
}

Prism command binding not executing

in my project i used wpf + prism.Inside a view,i must invoke a command inside a context menu, the command is defined into the viewmodel class.this is the view:
<UserControl x:Class="GrigoLync.Modules.LyncClient.Contatti.ContattiView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:infr="clr-namespace:GrigoLync.infrastructure.Model.Lync;assembly=GrigoLync.infrastructure"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" FontSize="12" Margin="5">Contatti</Label>
<TreeView x:Name="groupTreeView" Margin="10" ItemsSource="{Binding GruppiLync}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ContattiLync}">
<TextBlock Text="{Binding Path=Nome}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Uri}" Tag="{Binding}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header ="Invia messaggio istantaneo"
Command="{Binding PlacementTarget.Tag.SendInstantMessageCommand,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}, diag:PresentationTraceSources.TraceLevel=High}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
the following is the viewmodel class:
[Export(typeof(ContattiViewModel))]
[PartCreationPolicy(CreationPolicy.Shared)]
public class ContattiViewModel : NotificationObject
{
private readonly ILyncClientService lyncClientService;
private readonly IRegionManager regionManager;
private readonly IEventAggregator eventAggregator;
private List<GruppoLync> gruppiLync;
private ICommand sendInstantMessageCommand;
[ImportingConstructor]
public ContattiViewModel(ILyncClientService lyncClientService, IRegionManager regionManager, IEventAggregator eventAggregator)
{
if (lyncClientService == null)
{
throw new ArgumentNullException("lyncClientService");
}
if (regionManager == null)
{
throw new ArgumentNullException("regionManager");
}
if (eventAggregator == null)
{
throw new ArgumentNullException("eventAggregator");
}
this.lyncClientService = lyncClientService;
this.regionManager = regionManager;
this.eventAggregator = eventAggregator;
this.gruppiLync = lyncClientService.elencoGruppiLync();
this.sendInstantMessageCommand = new DelegateCommand<object>(this.SendInstantMessage);
//groupTreeView.DataItems = this.gruppiLync;
}
public ICommand SendInstantMessageCommand { get { return this.sendInstantMessageCommand; } }
public List<GruppoLync> GruppiLync
{
get
{
return this.gruppiLync;
}
}
private void SendInstantMessage(object aContattoLync)
{
//This point is not executed!
}
}
}
When i select from the user interface the menu item "Invia Messaggio istantaneo" the command is not invoke, This point is not executed
can help me please?!!!!
this is the GruppoLync class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace GrigoLync.infrastructure.Model.Lync
{
public class GruppoLync : INotifyPropertyChanged
{
private string nome;
public string Nome
{
get { return nome; }
set { nome = value;
OnPropertyChanged(new PropertyChangedEventArgs("Nome"));
}
}
private ObservableCollection<ContattoLync> contattiLync;
public ObservableCollection<ContattoLync> ContattiLync
{
get { return contattiLync; }
set
{
contattiLync = value;
OnPropertyChanged(new PropertyChangedEventArgs("ContattiLync"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
public GruppoLync(string nome, ObservableCollection<ContattoLync> contattiLync)
{
Nome = nome;
ContattiLync = contattiLync;
}
}
}
and this is ContattoLync class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using Microsoft.Practices.Prism.Commands;
namespace GrigoLync.infrastructure.Model.Lync
{
public class ContattoLync
{
public string Uri { get; set; }
public string Stato { get; set; }
}
}
I report also the trace log of the binding:
System.Windows.Data Warning: 54 : Created BindingExpression (hash=37997052) for Binding (hash=17879784)
System.Windows.Data Warning: 56 : Path: 'PlacementTarget.Tag.SendInstantMessageCommand'
System.Windows.Data Warning: 58 : BindingExpression (hash=37997052): Default mode resolved to OneWay
System.Windows.Data Warning: 59 : BindingExpression (hash=37997052): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 60 : BindingExpression (hash=37997052): Attach to System.Windows.Controls.MenuItem.Command (hash=47163810)
System.Windows.Data Warning: 64 : BindingExpression (hash=37997052): RelativeSource (FindAncestor) requires tree context
System.Windows.Data Warning: 63 : BindingExpression (hash=37997052): Resolve source deferred
System.Windows.Data Warning: 54 : Created BindingExpression (hash=18607377) for Binding (hash=17879784)
System.Windows.Data Warning: 56 : Path: 'PlacementTarget.Tag.SendInstantMessageCommand'
System.Windows.Data Warning: 58 : BindingExpression (hash=18607377): Default mode resolved to OneWay
System.Windows.Data Warning: 59 : BindingExpression (hash=18607377): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 60 : BindingExpression (hash=18607377): Attach to System.Windows.Controls.MenuItem.Command (hash=32025604)
System.Windows.Data Warning: 64 : BindingExpression (hash=18607377): RelativeSource (FindAncestor) requires tree context
System.Windows.Data Warning: 63 : BindingExpression (hash=18607377): Resolve source deferred
System.Windows.Data Warning: 65 : BindingExpression (hash=37997052): Resolving source
System.Windows.Data Warning: 68 : BindingExpression (hash=37997052): Found data context element: <null> (OK)
System.Windows.Data Warning: 71 : Lookup ancestor of type ContextMenu: queried ContextMenu (hash=46231978)
System.Windows.Data Warning: 70 : RelativeSource.FindAncestor found ContextMenu (hash=46231978)
System.Windows.Data Warning: 76 : BindingExpression (hash=37997052): Activate with root item ContextMenu (hash=46231978)
System.Windows.Data Warning: 106 : BindingExpression (hash=37997052): At level 0 - for ContextMenu.PlacementTarget found accessor DependencyProperty(PlacementTarget)
System.Windows.Data Warning: 102 : BindingExpression (hash=37997052): Replace item at level 0 with ContextMenu (hash=46231978), using accessor DependencyProperty(PlacementTarget)
System.Windows.Data Warning: 99 : BindingExpression (hash=37997052): GetValue at level 0 from ContextMenu (hash=46231978) using DependencyProperty(PlacementTarget): <null>
System.Windows.Data Warning: 104 : BindingExpression (hash=37997052): Item at level 1 is null - no accessor
System.Windows.Data Warning: 101 : BindingExpression (hash=37997052): Replace item at level 2 with {NullDataItem}
System.Windows.Data Warning: 78 : BindingExpression (hash=37997052): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 86 : BindingExpression (hash=37997052): TransferValue - using fallback/default value <null>
System.Windows.Data Warning: 87 : BindingExpression (hash=37997052): TransferValue - using final value <null>
System.Windows.Data Warning: 65 : BindingExpression (hash=18607377): Resolving source
System.Windows.Data Warning: 68 : BindingExpression (hash=18607377): Found data context element: <null> (OK)
System.Windows.Data Warning: 71 : Lookup ancestor of type ContextMenu: queried ContextMenu (hash=64235)
System.Windows.Data Warning: 70 : RelativeSource.FindAncestor found ContextMenu (hash=64235)
System.Windows.Data Warning: 76 : BindingExpression (hash=18607377): Activate with root item ContextMenu (hash=64235)
System.Windows.Data Warning: 105 : BindingExpression (hash=18607377): At level 0 using cached accessor for ContextMenu.PlacementTarget: DependencyProperty(PlacementTarget)
System.Windows.Data Warning: 102 : BindingExpression (hash=18607377): Replace item at level 0 with ContextMenu (hash=64235), using accessor DependencyProperty(PlacementTarget)
System.Windows.Data Warning: 99 : BindingExpression (hash=18607377): GetValue at level 0 from ContextMenu (hash=64235) using DependencyProperty(PlacementTarget): <null>
System.Windows.Data Warning: 104 : BindingExpression (hash=18607377): Item at level 1 is null - no accessor
System.Windows.Data Warning: 101 : BindingExpression (hash=18607377): Replace item at level 2 with {NullDataItem}
System.Windows.Data Warning: 78 : BindingExpression (hash=18607377): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 86 : BindingExpression (hash=18607377): TransferValue - using fallback/default value <null>
System.Windows.Data Warning: 87 : BindingExpression (hash=18607377): TransferValue - using final value <null>
That's because ContextMenus are on a separate logical tree.
This means they don't "inherit" the DataContext of the element they act upon.
A workaround is this:
<TextBlock Tag="{Binding}"> <!-- You may have to use RelativeSource on this Binding, depending on which DataContext you're trying to reach -->
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Name of the item"
Command="{Binding PlacementTarget.Tag.MyCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
This goes fetching MyCommand on the Tag of the PlacementTarget, in the ContextMenu of this MenuItem.
The PlacementTarget poiting to the TextBlock, and its Tag pointing to the DataContext of your view.
I solve the problem!!!!
<TreeView x:Name="groupTreeView" Margin="10" ItemsSource="{Binding GruppiLync}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ContattiLync}">
<TextBlock Text="{Binding Path=Nome}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Uri}"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TreeView}}">
<TextBlock.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header ="Invia messaggio istantaneo"
Command="{Binding SendInstantMessageCommand, diag:PresentationTraceSources.TraceLevel=High}"
CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Thanks to All!!!
I think to use binding with "RelativeSource={RelativeSource FindAncestor..." is a little strange, you'd rather bind directly to a property of the item set as the datacontext of your hierchical datatemplate.
check the datacontext of the menuitem to see if its the correct one

WPF : Binding cannot find source

I try to make my own ContentControl that derives from Control to fully understand dark wpf tree concepts. For now, i just implemented the logical part (Content) of the ContentControl.
My code behind :
[ContentProperty("Content")]
public class MyContentControl : Control
{
public MyContentControl()
{
}
public Object Content
{
get { return (Object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(Object), typeof(MyContentControl), new UIPropertyMetadata());
}
XAML :
<StackPanel x:Name="stackPanel">
<TextBlock Visibility="Collapsed" x:Name="textBlock" Text="Hello World"/>
<ContentControl>
<TextBlock Background="LightBlue" Text="{Binding Text, ElementName=textBlock}"/>
</ContentControl>
<local:MyContentControl>
<TextBlock Text="{Binding Text, ElementName=textBlock}"/>
</local:MyContentControl>
</StackPanel>
I got the following binding error :
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=textBlock'. BindingExpression:Path=Text; DataItem=null; target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
It is like the inner TextBlock can't go up in the logical tree and find the original textblock on which it should bind. I wasn't able to set myContentControl as the parent of the Content object.
Any idee?
Thanks for your time.
Jonas
Relevant question: Binding ElementName. Does it use Visual Tree or Logical Tree
The binding you want is not possible because the same instance of MyContentControl could theoretically be used somewhere else in the application where the element "textBlock" is not in the scope.
If you want to do this type of binding you could use a Resource instead:
xmlns:clr="clr-namespace:System;assembly=mscorlib"
<StackPanel>
<StackPanel.Resources>
<clr:String x:Key="MyText">Hanky Panky</clr:String>
</StackPanel.Resources>
<TextBlock Text="{StaticResource MyText}" />
<ContentControl>
<TextBlock Text="{Binding Source={StaticResource MyText}}" />
</ContentControl>
</StackPanel>
I Just had to apply FrameworkElement.AddLogicalChild and FrameworkElement.RemoveLogicalChild when the ContentChanged and the binding is correctly apply (Verified with WPF Inspector).
So all this is about LogicalTree (and maybe the xaml namescope is inherited from logical parent). The TextBlock inside MyContentControl get the MyContentControl as Parent when MyContentControl.AddLogicalChild(TextBlock) is called.
My Code :
[ContentProperty("Content")]
public class MyContentControl : Control
{
public MyContentControl()
{
Content = new UIElementCollection(this, this);
}
public Object Content
{
get { return (Object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(Object), typeof(MyContentControl), new UIPropertyMetadata(new PropertyChangedCallback(OnContentChanged)));
public static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyContentControl c = (MyContentControl)d;
if (e.OldValue != null)
{
c.RemoveLogicalChild(e.OldValue);
}
c.AddLogicalChild(e.NewValue);
}
protected override System.Collections.IEnumerator LogicalChildren
{
get
{
List<Object> l = new List<object>();
if (Content != null)
l.Add(Content);
return l.GetEnumerator();
}
}
}

Silverlight for Windows Phone: BindingExpression path error with user control - Property not found

Just hit a very odd issue with databinding which I cannot seem to get to the bottom of:
Scenario
An MVVM View model data bound to a parent form with two properties
public RelayCommand ClearFilteredCategories { get; private set; }
/// <summary>
/// The <see cref="ClearFilterText" /> property's name.
/// </summary>
public const string ClearFilterTextPropertyName = "ClearFilterText";
private string _clearFilterText = "Clear Filter";
/// <summary>
/// Sets and gets the ClearFilterText property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public string ClearFilterText
{
get
{
return _clearFilterText;
}
set
{
if (_clearFilterText == value)
{
return;
}
_clearFilterText = value;
RaisePropertyChanged(ClearFilterTextPropertyName);
}
}
Then I have a User control with Two dependency Properties, thus:
public partial class ClearFilterButton : UserControl
{
public ClearFilterButton()
{
// Required to initialize variables
InitializeComponent();
}
public string ClearFilterString
{
get { return (string)GetValue(ClearFilterStringProperty); }
set { SetValue(ClearFilterStringProperty, value); }
}
public RelayCommand ClearFilterAction
{
get { return (RelayCommand)GetValue(ClearFilterActionProperty); }
set { SetValue(ClearFilterActionProperty, value); }
}
public static readonly DependencyProperty ClearFilterStringProperty =
DependencyProperty.Register("ClearFilterString", typeof(string), typeof(ClearFilterButton), new PropertyMetadata("", ClearFilterString_PropertyChangedCallback));
public static readonly DependencyProperty ClearFilterActionProperty =
DependencyProperty.Register("ClearFilterAction", typeof(RelayCommand), typeof(ClearFilterButton), new PropertyMetadata(null, ClearFilterAction_PropertyChangedCallback));
private static void ClearFilterString_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//empty
}
private static void ClearFilterAction_PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//empty
}
}
and User Control XAML:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
mc:Ignorable="d"
x:Class="ATTCookBook.ClearFilterButton"
d:DesignWidth="75" d:DesignHeight="75"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Button HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="{x:Null}" Width="75" Height="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding ClearFilterAction, Mode=TwoWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Button.Background>
<ImageBrush Stretch="UniformToFill" ImageSource="/icons/appbar.refresh.rest.png"/>
</Button.Background>
</Button>
<TextBlock HorizontalAlignment="Center" Margin="0,0,0,8" TextWrapping="Wrap" Text="{Binding ClearFilterString, Mode=TwoWay}" VerticalAlignment="Bottom" FontSize="13.333" Height="18" Width="0" d:LayoutOverrides="VerticalAlignment"/>
</Grid>
Now when I add this User Control to the Main Page and databind the two View Model Properties through to the User control it gets very weird:
<local:ClearFilterButton Height="Auto" Width="Auto" ClearFilterAction="{Binding ClearFilteredCategories, Mode=TwoWay}" ClearFilterString="{Binding ClearFilterText, Mode=TwoWay}"/>
Because although the Databinding statements above seem fine, the Binding errors with:
System.Windows.Data Error: BindingExpression path error: 'ClearFilteredCategories' property not found on 'ATTCookBook.ClearFilterButton' 'ATTCookBook.ClearFilterButton' (HashCode=126600431). BindingExpression: Path='ClearFilteredCategories' DataItem='ATTCookBook.ClearFilterButton' (HashCode=126600431); target element is 'ATTCookBook.ClearFilterButton' (Name=''); target property is 'ClearFilterAction' (type 'GalaSoft.MvvmLight.Command.RelayCommand')..
System.Windows.Data Error: BindingExpression path error: 'ClearFilterText' property not found on 'ATTCookBook.ClearFilterButton' 'ATTCookBook.ClearFilterButton' (HashCode=126600431). BindingExpression: Path='ClearFilterText' DataItem='ATTCookBook.ClearFilterButton' (HashCode=126600431); target element is 'ATTCookBook.ClearFilterButton' (Name=''); target property is 'ClearFilterString' (type 'System.String')..
Which seems to indicate the View Model is trying to find the parent properties in the child user control?
I do not understand why this could be because I have set a Relative Data Context within the child user control to avoid this and the binding should be passing through the two dependency properties.
I was hoping to make the user control more generic later but cannot seem to even get it working in a basic fashion
Quick call out to you Silverlight Binding masters :D
In your UserControl you are resetting the Datacontext so now the expression
ClearFilterAction="{Binding ClearFilteredCategories, Mode=TwoWay}"
is not relative to the Page where you are using the UserControl but to the UserControl itself. You can encounter the same problem with ItemTemplate datacontext when you want to reference a common command declared on the VM of the Page and not on the object data bound to the template.
You can use a proxy:
ClearFilterAction="{Binding Source={StaticResource
DataContextProxy},Path=DataSource.ClearFilteredCategories}"
The proxy is declared as a Resource:
<Resources:DataContextProxy x:Key="DataContextProxy" />
and the code of the DataProxy class:
public class DataContextProxy : FrameworkElement
{
public DataContextProxy()
{
this.Loaded += new RoutedEventHandler(DataContextProxy_Loaded);
}
void DataContextProxy_Loaded(object sender, RoutedEventArgs e)
{
var binding = new Binding();
if (!String.IsNullOrEmpty(BindingPropertyName))
{
binding.Path = new PropertyPath(BindingPropertyName);
}
binding.Source = this.DataContext;
binding.Mode = BindingMode;
this.SetBinding(DataContextProxy.DataSourceProperty, binding);
}
public Object DataSource
{
get { return (Object)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(Object), typeof(DataContextProxy), null);
public string BindingPropertyName { get; set; }
public BindingMode BindingMode { get; set; }
}
Move the assignment of the data context to usercontrol's root DataGrid.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
mc:Ignorable="d"
x:Class="ATTCookBook.ClearFilterButton"
d:DesignWidth="75" d:DesignHeight="75">
<Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{Binding Parent, RelativeSource={RelativeSource Self}}" >
<Button HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="{x:Null}" Width="75" Height="75">
Resolved in the end with some thanks to the above, however the answer was a lot simpler.
All I had to do was remove any mention of Modes (remove Mode="TwoWay") and remove the DataContext setting from the user control and it just worked.
Just goes to show it's very easy to over-engineer a solution.
I Suspect by adding the Mode setting it was trying to pass the datacontext through the binding and that what it was throwing up (not a very helpful error message)
Hope this helps others. Just keep it simple and work up from there.
For those that are interested, I was basing the implementation from this very useful post - Silverlight UserControl Custom Property Binding

Custom Dependency Property - Binding doesn't work

I am preparing Custom UserControl, which will be located in a DataGrid. DataContext for this control will be row id (from DB) and Value of ComboBox located under the DataGrid.
This is how I embed my control in DataGrid:
<datagrid:DataGridTemplateColumn>
<datagrid:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<nmspc:MyControl IdT="{Binding id}" BValue="{Binding SelectedValue, ElementName=MyComboBox}" />
</DataTemplate>
</datagrid:DataGridTemplateColumn.CellTemplate>
</datagrid:DataGridTemplateColumn>
Values which I want to bind are id and selection of MyComboBox.
This is how MyControl Code behind looks like:
public static readonly DependencyProperty IdTProperty = DependencyProperty.Register("IdT", typeof(int), typeof(MyControl), new PropertyMetadata(IdTChanged));
private static void IdTChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//BoxResult obj = d as MyControl;
MessageBox.Show(e.NewValue.ToString());
//obj.IdT = (int)e.NewValue;
}
public int IdT
{
set {SetValue(IdTProperty, value); }
get {return (int)GetValue(TEIdProperty); }
}
public static readonly DependencyProperty BValueProperty = DependencyProperty.Register("BValue", typeof(string), typeof(MyControl), new PropertyMetadata(IdTChanged));
private static void BValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//BoxResult obj = d as MyControl;
MessageBox.Show(e.NewValue.ToString());
//obj.IdT = (string)e.NewValue;
}
public int BValue
{
set {SetValue(BValueProperty, value); }
get {return (int)GetValue(BValueProperty); }
}
Binding mechanism isn't working with my code. I am expecting that callbacks IdTChanged and BValueChanged will be called, but they won't.
Writing this code I based on this. Thanks in advance for all sugestions.
Regards,
Pawel
Edit:
This is how looks debug output:
System.Windows.Data Error: BindingExpression path error: 'id' property not found on 'My.MyControls.MyView.DataParams' 'My.MyControls.MyView.DataParams' (HashCode=42491497). BindingExpression: Path='id' DataItem='My.MyControls.MyView.DataParams' (HashCode=42491497); target element is 'My.MyControls.MyView.MyControl' (Name=''); target property is 'IdT' (type 'System.Int32')..
System.Windows.Data Error: BindingExpression path error: 'id' property not found on '852' 'System.Int32' (HashCode=852). BindingExpression: Path='id' DataItem='My.MyControls.MyView.DataParams' (HashCode=42491497); target element is 'My.MyControls.MyView.MyControl' (Name=''); target property is 'IdT' (type 'System.Int32')..
I found that is problem with RelativeSource. So in binding I set this value:
RelativeSource={RelativeSource TemplatedParent}
Problem with BindingExpression disappeard, but still it doesn't work (MessageBox in IdTChanged is not shown).
Some suggestions?

Resources