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.
Related
If I have the following binding
<TextBox Text="{Binding XXX.Name, ValidatesOnNotifyDataErrors=True}"/>
it doesn't work because only the DataContext implements INotifyDataErrorInfo and raises "XXX.Name" errors but ValidatesOnNotifyDataErrors tries to monitor XXX for error events not the data context.
However I am sure somebody could figure out how to write an attached property to do the following
<TextBox Grid.Column="5" Text="{Binding Binding.Name, c:ValidatesOnNotifyDataErrorsOnDataContext=True}"/>
where the data context is monitored not the child. Anybody got an idea how to start with that?
I think this is possible to implement, but because of the flexibility of bindings (RelativeSource, MultiBindings and whatnot) it would be difficult to make something like this that is truly robust. Personally, I think it would be cleaner to to implement INotifyDataErrorInfo at every level of the structure (and for parts of the structure that you don't own, like your Point example, use proxy classes that mirror the properties).
Anyway, Binding is a MarkupExtension, not a DependencyObject, which means attached properties can't be applied to it. You could inherit Binding to add your own properties, but this isn't very useful since it doesn't give you any overridable methods.
It shouldn't be necessary to extend Binding though, since all you want is a custom ValidationRule. Setting ValidatesOnNotifyDataErrors=True is equivalent to adding a NotifyDataErrorValidationRule:
<TextBox>
<TextBox.Text>
<Binding Path="XXX.Name">
<Binding.ValidationRules>
<NotifyDataErrorValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
</TextBox>
So you just need to replace NotifyDataErrorValidationRule with your own rule. If you override this Validate overload (which is passed the binding expression), you should be able to access the full binding path (through ParentBinding) and look up an error.
I have a converter in the code behind called StringToIntConverter I try using it in xaml binding like this where s is the project namespace:
Converter={s:StringToIntConverter}
But it says that it is missing an assembly reference. What am I doing wrong?
I know there is some way to put it as a resource and then reference the resource but I am not sure how to do it.
<Some.Resources>
<s:StringToIntConverter x:Key="StringToIntConverter"/>
</Some.Resources>
<!-- ... -->
Converter={StaticResource StringToIntConverter}
Curly braces indicate a markup extension, they cannot just be used arbitrarily to instantiate objects, but for convenience you could turn your converter into a markup extension.
Something like:
public class StringToIntConverter : MarkupExtension, IValueConverter
{
//...
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Then the code you used would work just fine!
Also note that you could use the binding in XML-element syntax to instantiate converters in place as well, e.g.
<TextBox>
<TextBox.Text>
<Binding Path="String">
<Binding.Converter>
<s:StringToIntConverter />
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>
Uhm - normally it goes something like this if I understand your question. You have created a converter right? In you XAML you need to add a reference to the assembly like this.
xmlns:converters="clr-namespace:Shared.Converters;assembly=Shared"
even if it is in the same assembly - something like...
xmlns:local="clr-namespace:ItemMaster"
Now you need to create a staticResource for whatever converter you want to use.
<converters:CostMethodToBooleanConverter x:Key="CostMethodToBooleanConverter"/>
Then you can use it.
IsEnabled="{Binding SelectedItem, Converter={StaticResource ReverseCostMethodToBooleanConverter}, ElementName=OemOriginalCostMethod}"/>
Does that help?
I am using MVVM/WPF and trying to do something seemingly simple, but cant find a clean solution.
I want to do the following:
When a property changes in the model (WPF Textbox text would be changed in this case), use a method to perform other operations on the UI relating to the property bound.
Currently i am using a multibinding on the tooltip (to get the textbox datacontext + binding path), but this is a bit of a hack.
<TextBox x:Name="textBox" Text="{Binding Model.MyProperty}">
<TextBox.ToolTip>
<MultiBinding Converter="{StaticResource brNewMultiConverter}">
<!-- This to trigger the converter in all required cases.
Without it, i cant get the event to fire when filling
the model initially
-->
<Binding ElementName="textBox" Path="Text" />
<!-- This has the properties i need, but wont fire without
the binding above -->
<Binding ElementName="textBox" />
</MultiBinding>
</TextBox.ToolTip>
</TextBox>
I would like to make something re-usable and maybe for different controls, hence i am not just using the textchanged event.
If anyone could point me in the right direction, it would be much appreciated.
OK, so far as your Multibinding there, what are you trying to accomplish there? I don't know what your converter is supposed to do, but can it not be done with an IValueConverter implementing class? I am assuming not, it looks like you are passing the textbox to the converter.
As far as having a method do several things when your model properties get updated, you can have the viewmodel subscribe to events on your model class. Just declare the object WithEvents (VB.NET) and add event handlers for On[PropertyName]Changed.
When implementing MVVM, I tend to treat the codebehind as a second class citizen. I do my best to push all logic off to the ViewModel or View if I can. I have almost completely stopped using Converters as much of that logic can be duplicated in ViewModels, and if it is something that I want to re-use, I usually just have a little helper class that gets whatever passed to it, does something, and passes it back out. I have never really had that great a relationship with IValueConverter...
Other than that, it is unclear exactly what you are trying to do. Could we get some more clarification?
It looks like you're trying to have the tooltip have the content of the textbox, if so why not just do this?
<TextBox Text="{Binding Model.MyProperty}" ToolTip="{Binding Model.MyProperty}"/>
If that's not what you want, but want the tooltip to change based on the value of the textbox then do that in your viewmodel e.g.
public class MyViewModel
{
string _MyProperty;
public string MyProperty
{
get { return _MyProperty;}
set
{
_MyProperty = value;
OnPropertyChanged("MyProperty");
OnPropertyChanged("MyToolTipProperty"); //force WPF to get the value of MyToolTipProperty
}
}
public string MyToolTipProperty
{
get
{
//return what you want
}
}
}
and then in your markup:
<TextBox Text="{Binding Model.MyProperty}" ToolTip="{Binding Model.MyToolTipProperty}"/>
Using the existing Binding class, we can write,
<TextBox Text="{Binding Email, Mode=TwoWay}"/>
So we can write anything as Email; there is no validity check by Binding itself. I started writing a class BindingMore deriving from Binding so that eventually I could write,
<TextBox Text="{local:BindingMore Email, Validate=SomeMethod, Mode=TwoWay}"/>
Where SomeMethod is some ICommand or delegate which will be triggered to validate the Email . That is my objective, and I've not written that yet.
As of now, I've written just this code,
public class BindingMore : System.Windows.Data.Binding
{
public BindingMore() : base()
{
}
public BindingMore(string path) : base(path)
{
}
}
So, at this stage, BindingMore is exactly equivalent to Binding, yet when I write
<TextBox Text="{local:BindingMore Email, Mode=TwoWay}"/>
It's giving me runtime error. But when I write,
<TextBox Text="{local:BindingMore Path=Email, Mode=TwoWay}"/>
It's working fine. Can anybody tell me why it's giving runtime error in the first case?
Unfortunately, the error is not shown. All it shows is this:
Also, I get the following error message from XAML (even when it builds perfectly and runs (in the second case)):
Type 'local:BindingMore' is used like
a markup extension but does not derive
from MarkupExtension.
Custom Markup Extensions are not supported in Silverlight. Try using an Attached Property approach or a Behavior.
I have several one-line binding already written and I'd like to keep it that way if possible and if it still is humanly readable.
Is there any way to rewrite this
<TextBox.Text>
<Binding Path="SomePath" NotifyOnValidationError="True" >
<Binding.ValidationRules>
<local:ValidationRule1></local:ValidationRule1>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
in one line?, like
<TextBox Text="{Binding Path=SomePath, [ValidationRule1...]}" />
I think there is no one-liner and besides the standard version is more readable.
There is no one-liner out of the box, but it's perfectly possible to make one yourself by taking advantage of markup extensions. More specifically, create a custom method of defining bindings for typical scenarios in your project.
A useful thing to note is that Binding class is itself a markup extension and thus provides an implementation of ProvideValue. It means that a custom binding markup extensions can just create or take a binding, fill out its values as needed and provide the value by the newly modified binding.
For example, if you commonly use a single validation rule and want to keep it in a one-liner, you may want to create an extension like this:
using System;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
namespace MyProject.Markup
{
public class SingleValidationBindingExtension : MarkupExtension
{
private Binding _binding;
public SingleValidationBindingExtension(Binding binding, ValidationRule validationRule)
{
_binding = binding;
_binding.ValidationRules.Add(validationRule);
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _binding.ProvideValue(serviceProvider);
}
}
}
Then, somewhere in your XAML:
<UserControl xmlns:ext="clr-namespace:MyProject.Markup;assembly=MyProject"
...>
<UserControl.Resources>
<local:ValidationRule1 x:Key="ValidationRule1"/>
</UserControl.Resources>
<!-- Note that you can customize other binding properties within the inner {Binding ...} markup -->
<TextBox Text="{ext:SingleValidationBinding {Binding SomePath}, {StaticResource ValidationRule1}}" />
</UserControl>
VoilĂ ! Now it took only one line to apply the validation rule to the binding.
Granted, having an extension specifically for a single-validation bindings may seem wasteful. However, with this simple proof-of-concept it's easy to expand upon the idea of custom binding markups. For example, you may group commonly used combinations of converters and validations rules into objects, define them as static resources and then apply them to bindings using a custom binding definition.