How to access converter from code in XAML? - wpf

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?

Related

Setting dependency properties on a static value converter from XAML

I have a value converter I wrote that allows me to bind against a property, test that property against a given (hard-coded) value, and return a brush based on if the test was true or false. The converter inherits from DependencyObject and implements IValueConverter. It exposes two dependency properties called PositiveBrush and NegativeBrush.
I declare it in XAML like this:
<UserControl.Resources>
<xyz:CBrushConverter x:Key="BrushConverter"
PositiveBrush="{DynamicResource Glyph.Resource.Brush.LightGreen}"
NegativeBrush="{DynamicResource Glyph.Resource.Brush.DarkGray}" />
</UserControl.Resources>
I can then adjust the color of a given element like this:
<TextBlock Foreground="{Binding SomeProperty, ConverterParameter='SomeValue', Converter={StaticResource BrushConverter}}" />
So in this example (making the assumption that SomeProperty returns a string) if the bound property 'SomeProperty' matches 'SomeValue' the converter will return the PositiveBrush as the Foreground (otherwise it will return the NegativeBrush).
So far so good - There may be other ways to skin this cat; but this has served me well for a long time and I don't really want to rock the boat.
What I would like to do however is declare my Positive and Negative brushes as part of my binding expression. Right now, if I wanted to use Red/Green and Blue/Yellow color combinations, I would need to declare two BrushConverters. But if I could declare the Positive/Negative brushes as part of the binding expression, I could use the same converter.
In pseudo-code, something like this (obviously this doesn't work):
<Grid Foreground="{Binding SomeProperty, ConverterParameter='SomeValue', Converter={StaticResource BrushConverter, BrushConverter.PositiveBrush='Red', BrushConverter.NegativeBrush='Green'}}" />
I did find a similar question on stack, How can I set a dependency property on a static resource? but it didn't explicitly address my question.
So... my google-foo is weak - I wasn't able to come up with the right search terms to dissect the Xaml binding syntax and work this out on my own, if it is even possible.
As always, any help is appreciated!
This should work:
<TextBlock>
<TextBlock.Foreground>
<Binding Path="SomeProperty" ConverterParameter="SomeValue">
<Binding.Converter>
<xyz:CBrushConverter PositiveBrush="Red" NegativeBrush="Green"/>
</Binding.Converter>
</Binding>
</TextBlock.Foreground>
</TextBlock>
Note however that you don't use the converter as static resource here. You would create a new converter instance for each Binding.
But if I could declare the Positive/Negative brushes as part of the binding expression, I could use the same converter.
You can't really do this. Converter is just a property of the Binding class. You still need to create an instance of the converter and set the dependency properties of this particular instance. What if you have several bindings that uses the same converter instance with different values for the PositiveBrush and NegativeBrush properties simultaneously?
You could define a converter instance inline though:
<TextBlock>
<TextBlock.Foreground>
<Binding Path="SomeProperty" ConverterParameter="SomeValue">
<Binding.Converter>
<xyz:CBrushConverter PositiveBrush="Green" NegativeBrush="Red" />
</Binding.Converter>
</Binding>
</TextBlock.Foreground>
</TextBlock>

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.

Databinding issue with textbox and accessors

I am wondering what am I missing? The binding is not displaying at all in the textbox. These are my codes:
XAML Namespace:
xmlns:c="clr-namespace:mySystem.Workspace"
DataContext and Resources:
<Grid.Resources>
<c:Parameter x:Key="mySource"/>
</Grid.Resources>
<Canvas>
<Canvas.DataContext>
<Binding Source="{StaticResource mySource}" />
</Canvas.DataContext>
Textbox:
<TextBox x:Name="TextBox" Width="159" Height="26" Canvas.Left="36" Canvas.Top="47">
<TextBox.Text>
<Binding Path="JobKey" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
The class:
namespace mySystem.Workspace
{
public class Parameter : Object
{
The accessors:
public BasePar JobKey
{
get { return jobKey; }
set { jobKey = value; }
}
There are lots of odd things here but the most obvious one that will get you working is that the Binding Path is case sensitive.
Change your binding to:
<Binding Path="JobKey" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>
This should get the binding working.
I'm also not sure what type BasePar is, or is meant to be, but unless you are doing something clever intentionally, just make it a standard type like string.
You should also probably not use the namespace System.Workspace, but something related to your own project.
After your response, the only thing I can guess that the BasePar object is intended for, is to be used within a DataTemplate, on an ItemsControl say. DataTemplates have the behaviour that when they do not know how to render an Object they will fall back the the Object's .ToString() method.
Now, in my comment I said that I don't think the TextBox can have a DataTemplate, and I believe this is true however I did find a trick at this Stackoverflow question which templates a content control and a textblock instead. The code is below:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:System.Workspace"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<c:Parameter x:Key="mySource"/>
<DataTemplate x:Key="MyDataTemplate">
<TextBlock Text="{Binding}"/>
</DataTemplate>
</Grid.Resources>
<Canvas>
<Canvas.DataContext>
<Binding Source="{StaticResource mySource}" />
</Canvas.DataContext>
<ContentControl
Content="{Binding Path=JobKey}"
ContentTemplate="{StaticResource MyDataTemplate}" />
</Canvas>
</Grid>
I don't have time right now to get the TextBox working - don't even know if it is possible, given my first few tries. However, this might help get you where you need to go.
But still - if I was me I'd just use simple binding to standard objects. I can't see the benefit of the BasePar class in this scenario.
What does the BasePar implementation look like? Have a look in the Debug Output window to see if you have a line like this:
System.Windows.Data Error: 1 : Cannot create default converter to perform 'two-way' conversions between types 'WpfApplication1.BasePar' and 'System.String'. Consider using Converter property of Binding. BindingExpression:Path=JobKey; DataItem='Parameter' (HashCode=14209755); target element is 'TextBox' (Name='TextBox'); target property is 'Text' (type 'String')
This is telling you that you are trying to bind to the property, but WPF cannot create a 2-way binding, because it cannot convert the text (you type into the TextBox) into a 'BasePar' object.
As per David's suggestion, you could bind to a primitive string type, or alternately (as per the warning message above) you could add a Converter to the binding to convert a string into a BasePar.
you need to make jobkey a DependencyProperty by deriving it from DependencyObject or derive your class from INotifyPropertyChanged and add all the notify code, etc.
if you do not do this, then you will not receive update notifications and your bindings wont work as expected.
Path="jobKey"
You need to bind to the property not the field, i.e. make that upper-case. Also: To debug bindings check the Output-window in Visiual Studio.

Binding ValidationRules in one line?

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.

WPF databinding/validation for URIs?

My current setup binds the Text property of my TextBox to a certain Uri object. I'd love to use WPF's inbuilt validation to detect invalid URIs, and proceed from there. But this doesn't seem to be working?
I would imagine that it would throw an exception if I entered, e.g., "aaaa" as a URI. Thus, triggering my current setup, which is supposed to detect exceptions like so:
<TextBox Grid.Column="1" Name="txtHouseListFile" DockPanel.Dock="Right" Margin="3">
<TextBox.Text>
<Binding Source="{StaticResource Settings}" Path="Default.HouseListFile" Mode="TwoWay">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Then I would imagine I could check the various Validation properties, like so?
Validation.GetHasError(this.txtHouseListFile)
But, this appears to not work. Maybe it doesn't throw exceptions when trying to convert? Or maybe my setup's wrong? Corrections to either would be great.
You can try create our own ValidationRule (inherit from ValidationRule). In this class, override Validate(...) and try create an URI object and catch the exceptions. In the catch, just set the e.Message to exception message.
(I am not too sure what is your binding source. Is it a URI object or a string?)
OK, I think I know what is going on. The binding doesn't know how to convert a string to a URI object (because the textbox Text property is a string). You need a converter to help him.
Try this:
Create a converter class (inherit from IValueConverter) that:
convert a string to a Uri using the Uri constructor
convert a Uri to a string (using one of the multiple getters)
Put your converter in the Binding. Then, the converver will throw an exception in the Uri constructor and your ExceptionValidationRule will catch it.
Look here to know how to use a converter.

Resources