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));
}
Related
In my WPF MVVM app I have a TextBox which has bound a validation rule.
In validation rule class I have below property:
public bool CanBeValidated { get; set; } = false;
Then in the view my TextBox has below validation rule bound (I only put the relevant part):
<TextBox.Text>
<Binding Path="myPath"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<vRules:RootPathValidationRule
ValidatesOnTargetUpdated="True"
CanBeValidated="{Binding Path=ValidationEnabled}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
In my view model the property is defined as below:
public bool ValidationEnabled
{
get { return _isValidationEnabled; }
set { this._isValidationEnabled = value; OnPropertyChanged(); }
}
So I receive below compilation error:
A 'Binding' cannot be set on the 'CanBeValidated' property of type
'MyPathValidatorRule'. A 'Binding' can only be set on a
DependencyProperty of a DependencyObject.
For first time when TextBox is loaded I want to avoid validation rule to fire until user edits it and avoid throwing a validation error since TextBox is empty.
Once user edits the TextBox, I would like to enable validation rule by performing a simple this.ValidationEnabled = true from view model.
How can I achieve this without using dependency properties? Is it possible?
You could create a wrapper class that derives from DependencyObject and exposes a dependency property. Then you add a CLR property to the ValidationRule class that returns an instance of this wrapper type:
public class RootPathValidationRule : ValidationRule
{
public Wrapper Wrapper { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
bool canBeValidated = Wrapper?.CanBeValidated == true;
...
}
}
public class Wrapper : DependencyObject
{
public static readonly DependencyProperty CanBeValidatedProperty =
DependencyProperty.Register(nameof(CanBeValidated), typeof(bool),
typeof(Wrapper));
public bool CanBeValidated
{
get { return (bool)GetValue(CanBeValidatedProperty); }
set { SetValue(CanBeValidatedProperty, value); }
}
}
Finally, you'll also need a binding proxy object that captures the DataContext where the source property is defined:
public class BindingProxy : System.Windows.Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new PropertyMetadata(null));
}
XAML:
<TextBox>
<TextBox.Resources>
<vRules:BindingProxy x:Key="proxy" Data="{Binding}"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="myPath"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<vRules:RootPathValidationRule ValidatesOnTargetUpdated="True">
<vRules:RootPathValidationRule.Wrapper>
<vRules:Wrapper CanBeValidated="{Binding Data.ValidationEnabled,
Source={StaticResource proxy}}"/>
</vRules:RootPathValidationRule.Wrapper>
</vRules:RootPathValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Please refer to this article for details.
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
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.
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?
Suppose you have a class inheriting from ValidationRule:
public class MyValidationRule : ValidationRule
{
public string ValidationType { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {}
}
In XAML you are validating like this:
<ComboBox.SelectedItem>
<Binding Path="MyPath" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<qmvalidation:MyValidationRule ValidationType="notnull"/>
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
Which works and everything is ok.
But suppose now, you want to have ValidationType="{Binding MyBinding}" where MyBinding comes from DataContext.
For this purpose I would need to make MyValidationRule as a DependencyObject and add a Dependency Property.
I've tried to write a class that is DependencyObject, and bind it. There are 2 problems though.. the ValidationRule DOES NOT have the DataContext from the Combobox / Item.
Do you have any ideas, on how to solve that?
Since ValidationRule does not inherit from DependencyObject you cannot create a DependecyProperty in your custom validation class.
However as explained in this link you can have a normal property in your validation class which is of a type that inherits from DependecyObject and create a DependencyProperty in that class.
For example here is a custom ValidationRule class that support bindable property:
[ContentProperty("ComparisonValue")]
public class GreaterThanValidationRule : ValidationRule
{
public ComparisonValue ComparisonValue { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string s = value?.ToString();
int number;
if (!Int32.TryParse(s, out number))
{
return new ValidationResult(false, "Not a valid entry");
}
if (number <= ComparisonValue.Value)
{
return new ValidationResult(false, $"Number should be greater than {ComparisonValue}");
}
return ValidationResult.ValidResult;
}
}
ComparisonValue is a simple class that inherits from DependencyObject and has a DependencyProperty:
public class ComparisonValue : DependencyObject
{
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value),
typeof(int),
typeof(ComparisonValue),
new PropertyMetadata(default(int));
This solves the original problem but unfortunately brings two more problems:
The binding does not work correctly since the ValidationRules is not part of visual tree and therefore cannot get the bound property correctly. For example this naive approach will not work:
<TextBox Name="TextBoxToValidate">
<TextBox.Text>
<Binding Path="ViewModelProperty" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<numbers:GreaterThanValidationRule>
<numbers:ComparisonValue Value="{Binding Text, ElementName=TextBoxToValidate}"/>
</numbers:GreaterThanValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Instead a proxy object should be used as explained in this answer:
<TextBox Name="TextBoxToValidate">
<TextBox.Resources>
<bindingExtensions:BindingProxy x:Key="TargetProxy" Data="{Binding Path=Text, ElementName=TextBoxToValidate}"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="ViewModelProperty" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<numbers:GreaterThanValidationRule>
<numbers:ComparisonValue Value="{Binding Data, Source={StaticResource TargetProxy}}"/>
</numbers:GreaterThanValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
BindingProxy is a simple class:
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(nameof(Data), typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
If the property in custom ValidationRule is bound to another object's property, the validation logic for the original property will not fire when that other object's property changes.
To solve this problem we should update the binding when the ValidationRule's bound property is updated. First we should bind that property to our ComparisonValue class. Then, we can update the source of the binding when the Value property changes:
public class ComparisonValue : DependencyObject
{
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value),
typeof(int),
typeof(ComparisonValue),
new PropertyMetadata(default(int), OnValueChanged));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ComparisonValue comparisonValue = (ComparisonValue) d;
BindingExpressionBase bindingExpressionBase = BindingOperations.GetBindingExpressionBase(comparisonValue, BindingToTriggerProperty);
bindingExpressionBase?.UpdateSource();
}
public object BindingToTrigger
{
get { return GetValue(BindingToTriggerProperty); }
set { SetValue(BindingToTriggerProperty, value); }
}
public static readonly DependencyProperty BindingToTriggerProperty = DependencyProperty.Register(
nameof(BindingToTrigger),
typeof(object),
typeof(ComparisonValue),
new FrameworkPropertyMetadata(default(object), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
}
The same proxy problem in the first case also exists here. Therefore we should create another proxy object:
<ItemsControl Name="SomeCollection" ItemsSource="{Binding ViewModelCollectionSource}"/>
<TextBox Name="TextBoxToValidate">
<TextBox.Resources>
<bindingExtensions:BindingProxy x:Key="TargetProxy" Data="{Binding Path=Items.Count, ElementName=SomeCollection}"/>
<bindingExtensions:BindingProxy x:Key="SourceProxy" Data="{Binding Path=Text, ElementName=TextBoxToValidate, Mode=TwoWay}"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="ViewModelProperty" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<numbers:GreaterThanValidationRule>
<numbers:ComparisonValue Value="{Binding Data, Source={StaticResource TargetProxy}}" BindingToTrigger="{Binding Data, Source={StaticResource SourceProxy}}"/>
</numbers:GreaterThanValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
In this case the Text property of TextBoxToValidate is validated against the Items.Count property of SomeCollection. When the number of items in the list changes, the validation for the Text property will be triggered.