Binding on a Non-UIElement - wpf

I am having problems with Binding. Since RelativeSource needs the visual tree to travel up and find the desired Ancestor, you are only allowed to use it on an UIElement but I am trying to do a RelativeSource binding on an Non-UIElement, such as is a ValidationRule, which as you all know isnt inside the VisualTree nor its UIElement. As you can expect the binding breaks. RelativeSource couldn't be found because like i said there is no VisualTree or LogicalTree available. I need to make it work though.
Here is an example of XAML:
<StackPanel DataContext{Binding}>
<Grid>
<ContentControl Content{Binding MVPart1>
<TextBox>
<TextBox.Text>
<Binding Path="VMPart1Property1">
<Binding.ValidationRules>
<my:MyValidationRule>
<my:ValidationRule.DOC>
<my:DepObjClass DepProp={Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}}}/>
</my:ValidationRule.DOC>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</ContentControl>
</Grid>
</StackPanel>
So basically MyValidationRule is derivering from ValidationRule class, but thats not UIElement nor DependencyObject and therefore I had to create a class which derivates from DependencyObject called DepObjClass to be able to write down the xaml binding expression.
Here is code:
public class MyValidationRule : ValidationRule
{
public DepObjClass DOC
{
get;
set;
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string text = value as string;
if (!string.IsNullOrEmpty(text))
{
return new ValidationResult(true, string.Empty);
}
return new ValidationResult(false, "Not working blahhh");
}
}
public class DepObjClass : DependencyObject
{
public object DepProp
{
get
{
return (object)GetValue(DepPropProperty);
}
set
{
SetValue(DepPropProperty, value);
}
}
public static DependencyProperty DepPropProperty
= DependencyProperty.Register(typeof(object), typeof(DepObjClass)......);
}
Now to sum up. MyValidatonRule is not UIElement its not DependencyObject but it has a property of a type that is, hence why the xaml binding expression compiles.
When I run the application the binding itself isnt working because StackPanel couldnt be found because ValidationRule doesnt have VisualTree nor my validation rule participates in Logical or Visual Tree.
The question is how do I make such case work, how to find StackPanel from an Non-UIElement such as my ValidationRule?
I appologize for my code not comipiling but I hope you can understand what I am trying to do.
I am giving 50 points to you guys for the right answer.

You can do the following:
Create a helper component which derives from Freezable and defines a DependencyProperty for what you want to bind.
Create a ValidationRule with a property which takes an object of the helper component, similar to what you have done already.
Declare an instance of the helper component in the Resources of an object which can bind to whatever you want to bind. Freezable and its derived classes inherit the binding context (the location in the logical tree) of any control in whose Resources they are declared, so there you can create your binding.
When declaring the ValidationRule, use {StaticResource} to assign the helper component to the property in the ValidationRule. StaticResource works without a binding context, as long as the resource is declared before it is used.
The XAML would look like this:
<StackPanel>
<StackPanel.Resources>
<my:Helper x:Key="helper" ValProperty="{Binding}"/>
</StackPanel.Resources>
<Grid>
<TextBox DataContext="{Binding MVPart1}">
<TextBox.Text>
<Binding Path="VMPart1Property1">
<Binding.ValidationRules>
<my:MyValidationRule Helper="{StaticResource helper}"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</StackPanel>

Related

Difference Source and Datacontext in wpf

DataContext and Source seem to be very similar to me.
What are the advantages and disadvantages?
When to use which one?
With Source:
<TextBlock Text="{Binding Name, Source={StaticResource Person}}" />
Or the solution with DataContext:
public partial class DataContextSample : Window
{
public string Name {get; set;}
public DataContextSample()
{
InitializeComponent();
this.DataContext = this;
}
}
<TextBlock Text="{Binding Name}" />
A binding with out a specified Source binds to the DataContext property of the element.
The DataContext is a special property which, if not set, is redirected to the element's parent's DataContext. This prevents duplicate xaml (always setting the Source in every binding) and makes all bindings relative so it is easier to change the UI without having to adjust all Sources in the bindings.

Can you use a Binding ValidationRule within 1 line in xaml?

I don't know the correct wording to describe what I'm trying to do here... so I'll just show it.
This xaml I know works:
<TextBox>
<TextBox.Text>
<Binding Path="Location" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<domain:NotEmptyValidationRule ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
But this is pretty verbose. I would like to do it in a way similar to this...
<TextBox Text={Binding Path=Location, UpdateSourceTrigger=PropertyChanged,
ValidationRules={domain:NotEmptyValidationRuleMarkup ValidateOnTargetUpdated=True}}"/>
I made a class called NotEmptyValidationRuleMarkup that returns an instance of NotEmptyValidationRule, and it sort-of works. Project builds just fine, it runs just fine, everything works exactly as I expect it to. However, I can no longer view my window in the designer. It gives me an Invalid Markup error because The property "ValidationRules" does not have an accessible setter.. And it's true, ValidationRules does not have a setter. If I try to set ValidationRules through code in C# I get a compile error. But for some reason when I assign it in XAML it actually does build and run just fine. I'm confused. Is there a way I can make this work without jacking up the design view of my window?
Even though the xaml interpreter happens to turn the markup extension into something working, this is not really supported.
See MSDN - Binding Markup Extension
The following are properties of Binding that cannot be set using the Binding markup extension/{Binding} expression form.
...
ValidationRules: the property takes a generic collection of ValidationRule objects. This could be expressed as a property element in a Binding object element, but has no readily available attribute-parsing technique for usage in a Binding expression. See reference topic for ValidationRules.
However, let me suggest a different approach: instead of nesting the custom markup extension in the binding, nest the binding in a custom markup extension:
[ContentProperty("Binding")]
[MarkupExtensionReturnType(typeof(object))]
public class BindingEnhancementMarkup : MarkupExtension
{
public BindingEnhancementMarkup()
{
}
public BindingEnhancementMarkup(Binding binding)
{
Binding = binding;
}
[ConstructorArgument("binding")]
public Binding Binding { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
Binding.ValidationRules.Add(new NotEmptyValidationRule());
return Binding.ProvideValue(serviceProvider);
}
}
And use as follows:
<TextBox Text="{local:BindingEnhancementMarkup {Binding Path=Location, UpdateSourceTrigger=PropertyChanged}}"/>
Ofcourse, for production you may want to add a few more checks in the markup extension instead of just assuming everything is in place.

Using a variable in XAML binding expression

I'm building a control that can edit POCOs. There is a descriptor collection for the fields within the POCO that need to be edited and I'm binding a ListBox's ItemsSource to this collection. Amongst other things, the descriptor gives me the ability to select a suitable DataTemplate and the variable name in the POCO that this ListBox item should edit.
My ListBox is built like this:
<ListBox ItemsSource="{Binding ColumnCollection, ElementName=root}">
<ListBox.Resources>
<DataTemplate x:Key="TextTemplate">
<StackPanel>
<TextBlock Text="{Binding DisplayName}" />
<!-- !!! Question about following line !!! -->
<TextBox Text="{Binding ElementName=vm.CurentEditing, Path=PathName}" />
</StackPanel>
</DataTemplate>
<!-- Details omitted for brevity -->
<DataTemplate x:Key="PickListTemplate" />
<DataTemplate x:Key="BooleanTemplate" />
</ListBox.Resources>
<ListBox.ItemTemplateSelector>
<local:DataTypeSelector
TextTemplate="{StaticResource TextTemplate}"
PickListTemplate="{StaticResource PickListTemplate}"
BooleanTemplate="{StaticResource BooleanTemplate}"
/>
</ListBox.ItemTemplateSelector>
</ListBox>
It is the TextBox binding expression in the "TextTemplate" that I am having problems with. The problem is that "PathName" should not be taken as a literal string, but is the name of a string property in the ColumnDescription class (the collection type of ColumnCollection used for ListBox.ItemsSource), which gives the name of the POCO property I want to bind to (the POCO is "vm.CurrentEditing").
Is there some way to use the value of a property in XAML as input to a binding expression, or will I have to resort to code behind?
(Incidentally, specifying the ElementName as "x.y" as I have done above also seems to be invalid. I assume the "y" part should be in Path but that's currently taken up with my property name...!)
So you want to bind TextBox.Text to Property X of Object Y, where X and Y both change at runtime.
It sounds like what you want to do is something analogous to ListBox.DisplayMemberPath: You can bind a string or PropertyPath property to DisplayMemberPath and it'll work. The way I've done stuff like that is to have a dependency property of type String or PropertyPath, and programatically create a binding from that to whatever property.
So, I wrote an attached property which creates a binding.
public class POCOWrangler
{
#region POCOWrangler.BindPropertyToText Attached Property
public static String GetBindPropertyToText(TextBox obj)
{
return (String)obj.GetValue(BindPropertyToTextProperty);
}
public static void SetBindPropertyToText(TextBox obj, PropertyPath value)
{
obj.SetValue(BindPropertyToTextProperty, value);
}
public static readonly DependencyProperty BindPropertyToTextProperty =
DependencyProperty.RegisterAttached("BindPropertyToText", typeof(String), typeof(POCOWrangler),
new PropertyMetadata(null, BindPropertyToText_PropertyChanged));
private static void BindPropertyToText_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is String && d is TextBox)
{
var tb = d as TextBox;
var binding = new Binding((String)e.NewValue);
// The POCO object we're editing must be the DataContext of the TextBox,
// which is what you've got already -- but don't set Source explicitly
// here. Leave it alone and Binding.Source will be updated as
// TextBox.DataContext changes. If you set it explicitly here, it's
// carved in stone. That's especially a problem if this attached
// property gets initialized before DataContext.
//binding.Source = tb.DataContext;
binding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(tb, TextBox.TextProperty, binding);
}
}
#endregion POCOWrangler.BindPropertyToText Attached Property
}
And I wrote a quick example thing: There's a little class named Foo that has a Name property, and a viewmodel with two properties, Foo Foo and String DisplayPathName. It works! Of course, this depends on default TextBox editing behavior for whatever type the property happens to be. I think that will get you the same results as if you'd bound explicitly in XAML, but it sitll won't always necessarily be just what you want. But you could very easily go a little nuts and add some triggers in the DataTemplate to swap in different editors, or write a DataTemplateSelector.
I stuffed ViewModel.Foo in a ContentControl just to get a DataTemplate into the act, so that the TextBox gets his DataContext in the same manner as yours.
Note also that I'm getting DisplayPathName by a relative source from something outside the DataContext object -- it's not a member of Foo, of course, it's a member of the viewmodel.
C#
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel {
DisplayPathName = "Name",
Foo = new Foo { Name = "Aloysius" }
};
}
XAML
<ContentControl
Content="{Binding Foo}"
>
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBox
local:POCOWrangler.BindPropertyToText="{Binding
DataContext.DisplayPathName,
RelativeSource={RelativeSource AncestorType=ContentControl}}"
/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
That was fun.

Populating a Collection Property in XAML: Item DataContext is null

I have a control that has an ObservableCollection of one or more key-value pairs. That can be provided as a ready-made ObservableCollection by the ViewModel bound as usual (Keys={Binding KeyCollection} -- which works perfectly), but I'd like to be able to define it in XAML as well:
<foo:KeyControl>
<foo:KeyItem Key="ID" Value="{Binding ID}" />
<foo:KeyItem Key="HatSize" Value="{Binding HatSize}" />
</foo:KeyControl>
KeyItem is derived from FrameworkElement, and the properties Key and Value are dependency properties. I've got a ContentPropertyAttribute on KeyControl, and that's working fine: The correct collection property is populated, and the Key properties (the ones with literal values, not bindings) are initialized as in the XAML.
The trouble is that the bindings for the Value properties don't work. They always assign null to the property. I think that's because the KeyItem instances have a null DataContext.
Also, RelativeSource FindAncestor thinks there aren't any ancestors to find:
<foo:KeyItem Type="ID"
Value="{Binding Path=DataContext.ID,
RelativeSource={RelativeSource FindAncestor, AncestorType=foo:MyView},
diag:PresentationTraceSources.TraceLevel=High}" />
When new KeyItem instances are added to the ObservableCollection, I've tried setting their DataContext to be that of the control, but the control's DataContext is always null at that point (?!) if they're defined in XAML.
What am I missing?
UPDATE
The content of the answer was in a linked article by Thomas Levesque, so in case that goes offline, here's the fix: You create a proxy as a resource. Where you define the resource, the control's DataContext is in scope. In the bindings on the collection item properties, you can get to the resource.
C#:
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));
}
XAML:
<foo:KeyControl>
<foo:KeyControl.Resources>
<foo:BindingProxy x:Key="proxy" Data="{Binding}" />
</foo:KeyControl.Resources>
<foo:KeyItem Key="ID" Value="{Binding Data.ID,
Source={StaticResource proxy}}" />
<foo:KeyItem Key="HatSize" Value="{Binding Data.HatSize,
Source={StaticResource proxy}}" />
</foo:KeyControl>
Bit of a kludge, but it works. I think I may just stick with binding the collection from the ViewModels, though.
For search purposes, I was getting the "Framework mentor not found" error on this when the DataContexts were null.
I had a similar problem with the datacontext not inheriting and I solved my problem using the proxy technique described here
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
Hope this helps

ValidationRule To Enforce Unique Name

I'm trying to write a custom WPF ValidationRule to enforce that a certain property is unique within the context of a given collection. For example: I am editing a collection of custom objects bound to a ListView and I need to ensure that the Name property of each object in the collection is unique. Does anyone know how to do this?
First, I'd create a simple DependencyObject class to hold your collection:
class YourCollectionType : DependencyObject {
[PROPERTY DEPENDENCY OF ObservableCollection<YourType> NAMED: BoundList]
}
Then, on your ValidationRule-derived class, create a property:
YourCollectionType ListToCheck { get; set; }
Then, in the XAML, do this:
<Binding.ValidationRules>
<YourValidationRule>
<YourValidationRule.ListToCheck>
<YourCollectionType BoundList="{Binding Path=TheCollectionYouWantToCheck}" />
</YourValidationRule.ListToCheck>
</YourValidationRule>
</Binding.ValidationRules>
Then in your validation, look at ListToCheck's BoundList property's collection for the item that you're validating against. If it's in there, obviously return a false validation result. If it's not, return true.
I would only create a custom dependency object if there were other properties I wanted to bind to the rule. Since in this case all we're doing is attaching a single collection of values to check against, I made my <UniqueValueValidationRule.OtherValues> property a <CollectionContainer>.
From there, to get past the problem of the DataContext not being inherited, <TextBox.Resources> needed to have a <CollectionViewSource> to hold the actual binding and give it a {StaticResource} key, which OtherValues could then use as binding source.
The validation rule itself then need only loop through OtherValues.Collection and perform equality checks.
Observe:
<TextBox>
<TextBox.Resources>
<CollectionViewSource x:Key="otherNames" Source="{Binding OtherNames}"/>
</TextBox.Resources>
<TextBox.Text>
<Binding Path="Name">
<Binding.ValidationRules>
<t:UniqueValueValidationRule>
<t:UniqueValueValidationRule.OtherValues>
<CollectionContainer Collection="{Binding Source={StaticResource otherNames}}"/>
</t:UniqueValueValidationRule.OtherValues>
</t:UniqueValueValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

Resources