Validation Error template doesn't show the Error result WPF - c#

I am new in WPF I want validate my IP address but I have a problem: when I try to show the error message, it shows me only an empty red border.
Here is the ControlTemplate and all the code:
<Window x:Class="SOTCBindingValidation.Window1"
x:Name="This"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SOTCBindingValidation"
Title="SOTC Validation Test" Height="150" Width="400">
<Window.Resources>
<local:ErrorsToMessageConverter x:Key="eToMConverter"/>
<ControlTemplate x:Key="customvalidatortemplate">
<StackPanel Orientation="Horizontal">
<Border BorderThickness="1" BorderBrush="Red" VerticalAlignment="Top">
<Grid>
<AdornedElementPlaceholder x:Name="adorner" Margin="-1"/>
</Grid>
</Border>
<Border x:Name="errorBorder" Background="Red" Margin="8,0,0,0"
CornerRadius="0" IsHitTestVisible="False">
<TextBlock Text="{Binding ElementName=AddressBox,
Path=(Validation.Errors),
Converter={StaticResource eToMConverter}}"
Foreground="White" FontFamily="Segoe UI"
Margin="8,2,8,3" TextWrapping="Wrap"
VerticalAlignment="Center"/>
</Border>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<StackPanel Margin="5">
<TextBlock Margin="2">Enter An IPv4 Address:</TextBlock>
<TextBox x:Name="AddressBox"
Validation.ErrorTemplate="{StaticResource customvalidatortemplate}"
Margin="0,0,235.5,0">
<TextBox.Text>
<Binding ElementName="This" Path="IPAddress"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:IPv4ValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</Window>
ErrorsToMessageConverter.cs file :
public class ErrorsToMessageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var sb = new StringBuilder();
var errors = value as ReadOnlyCollection<ValidationError>;
if (errors != null)
{
foreach (var e in errors.Where(e => e.ErrorContent != null))
{
sb.AppendLine(e.ErrorContent.ToString());
}
}
return sb.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
IPv4ValidationRule.cs file :
public class IPv4ValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var str = value as string;
if (String.IsNullOrEmpty(str))
{
return new ValidationResult(false,
"Please enter an IP Address.");
}
var parts = str.Split('.');
if (parts.Length != 4)
{
return new ValidationResult(false,
"IP Address should be four octets, seperated by decimals.");
}
foreach (var p in parts)
{
int intPart;
if (!int.TryParse(p, NumberStyles.Integer, cultureInfo.NumberFormat, out intPart))
{
return new ValidationResult(false,
"Each octet of an IP Address should be a number.");
}
if (intPart < 0 || intPart > 255)
{
return new ValidationResult(false,
"Each octet of an IP Address should be between 0 and 255.");
}
}
return new ValidationResult(true, null);
}
}

I've found the solution (after a sleep:). In fact the exact element source you have to bind to can be accessed via the AdornedElementPlaceholder. It has a property called AdornedElement, TemplateBinding does not work in this case because TemplatedParent does not point to the TextBox, it's just another Control which is used for ErrorTemplate control. So the code should be like this:
<TextBlock Text="{Binding ElementName=adorner,
Path=AdornedElement.(Validation.Errors),
Converter={StaticResource eToMConverter}}"
Foreground="White" FontFamily="Segoe UI" Margin="8,2,8,3"
TextWrapping="Wrap" VerticalAlignment="Center"/>
Note about how we set the attached property Validation.Errors for the AdornedElement. Also note about the name adorner which is exactly the name you set for the AdornedElementPlaceholder. I've made a demo and surely it should work.

Related

RadioButton Not Checked on Start

I have an application with four radio buttons to select the mode I am operating.
All the 4 Radio Buttons are binded to the same property. When i start the program the none of the radio button is checked.
This is the code for the radio buttons:
<GroupBox Grid.Row="0" Margin="10,10,10,10" FontSize="16"
FontWeight="Bold">
<GroupBox.Header>Tipo di Rientro</GroupBox.Header>
<StackPanel>
<RadioButton Name="RdBtnExternalEntry" FontSize="12"
FontWeight="Normal" GroupName="SelectionType" IsChecked="{Binding
Path=CurrentOption, Mode=TwoWay, Converter={StaticResource
enumConverter}, ConverterParameter=ExternalEntry}">Entrata da
Esterno</RadioButton>
<RadioButton Name="RdBtnEntryAfterCheck" FontSize="12"
FontWeight="Normal" GroupName="SelectionType" IsChecked="{Binding
Path=CurrentOption, Mode=TwoWay, Converter={StaticResource
enumConverter}, ConverterParameter=EntryAfterCheck}">Rientro dopo
visione</RadioButton>
<RadioButton Name="RdBtnEntryMissingShipping" FontSize="12"
FontWeight="Normal" GroupName="SelectionType" IsChecked="{Binding
Path=CurrentOption, Mode=TwoWay, Converter={StaticResource
enumConverter}, ConverterParameter=EntryMissingShipping}">Rientro
per mancata Spedizione</RadioButton>
<RadioButton Name="RdBtnEntryAfterPicking" FontSize="12"
FontWeight="Normal" GroupName="SelectionType" IsChecked="{Binding
Path=CurrentOption, Mode=TwoWay, Converter={StaticResource
enumConverter}, ConverterParameter=EntryAfterPicking}">Rientro
dopo Picking</RadioButton>
</StackPanel>
</GroupBox>
This is the property:
public RadioOptions CurrentOption
{
get => _currentOption;
set
{
_currentOption = value;
NewLoadCommand.RaiseCanExecuteChanged();
ConfirmCommand.RaiseCanExecuteChanged();
if (value == RadioOptions.ExternalEntry)
{
_selectedStockUnitCode = PalletToDo;
SelectedLoadnumber = LoadToDo;
RaisePropertyChanged("SelectedLoadnumber");
RaisePropertyChanged("SelectedStockUnitCode");
}
else
{
SelectedLoadnumber = "0";
RaisePropertyChanged("SelectedLoadnumber");
}
RaisePropertyChanged("SelectedStockUnitCodeIsEnabled");
RaisePropertyChanged("SelectedLoadnumberIsEnabled");
}
}
And this is the converter:
public class EnumMatchToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value == null || parameter == null)
return false;
string checkValue = value.ToString();
string targetValue = parameter.ToString();
return checkValue.Equals(targetValue,
StringComparison.InvariantCultureIgnoreCase);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value == null || parameter == null)
return null;
bool useValue = (bool)value;
string targetValue = parameter.ToString();
if (useValue)
return Enum.Parse(targetType, targetValue);
return Binding.DoNothing;
}
}
these are the radio options:
public enum RadioOptions { ExternalEntry, EntryAfterCheck, EntryMissingShipping, EntryAfterPicking }
I expect the first combobox to be checked at the start of the program
Resolved! I was, by mistake launching another copy of my window as a popup istance messing up with the way xaml manages the radio buttons. I deleted one of the istance and then it worked fine.

C# Wpf Binding type adapter

As of now, i assign the image of a TreeView item using a direct binding to the image's source:
<DataTemplate DataType="{x:Type local:GeoPoint}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Color}" Height="32" />
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
the Color binding is referring to string containing the path to the PNG, something like "/Resources/red.png"
i would like to make the Color variable of custom type "MarkerColor", an enum containing several colors, and have the image source binding reference this value, so that if
Color = MarkerColor.green; the binding would reference "/Resources/green.png"
Note that the name of the PNG is not necessarily the same as the name of MarkerColor, an "adapter" should be used to convert the type
I know how to do this in Java Android SDK, but not really sure on how to achive this in Wpf
You could create a converter that knows how to convert the enumeration value to a valid resource:
public class ColorResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
MarkerColor color = (MarkerColor)value;
Uri uri;
switch(color)
{
case MarkerColor.Green:
uri = new Uri("Resources/green.png");
break;
case MarkerColor.Red:
uri = new Uri("Resources/red.png");
break;
//...
default:
uri = new Uri("Resources/default.png");
break;
}
return new BitmapImage(uri);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Usage:
<DataTemplate DataType="{x:Type local:GeoPoint}">
<DataTemplate.Resources>
<local:ColorResourceConverter x:Key="ColorResourceConverter" />
</DataTemplate.Resources>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Color, Converter={StaticResource ColorResourceConverter}}" Height="32" />
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>

WPF Converter can't access base property

Im trying to achive something like here WPF DataGrid Grouping with sums and other fields to sum up 'quantity' property from items in a group. But it throws exception in foreach loop that it cant convert 'items' to my Type ('OrderItem' of which i thought items should be...)
C#
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is ReadOnlyObservableCollection<Object>)
{
var items = ((ReadOnlyObservableCollection<Object>) value);
Decimal total = 0;
foreach (OrderItem gi in items )
{
total += gi.quantity;
}
return total.ToString();
}
return "0";
}
XAML
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" Margin="5"/>
<TextBlock Text="Count" Margin="5" />
<TextBlock Text="{Binding Path=Items, Converter={StaticResource additionConverter}}" Margin="5"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
and resource
<local:AdditionConverter x:Key="additionConverter" />
how to get access to the base element of 'value'? I puted a breakpoint and it is there.
Sorry if i messed up something, i'm new to WPF.
Replace this :
foreach (OrderItem gi in items )
{
total += gi.quantity;
}
with :
foreach (CollectionViewGroup group in items)
{
foreach(OrderItem item in group.Items)
{
total += item.quantity;
}
}
and tell if this solves your problem.

Using MultiBinding to calculate sum of multiple textboxes

Based on this previously answered question, I'm trying to create an IMultiValueConverter that will allow the Text property of a TextBox in WPF to be bound to the sum of several other TextBox values. I've mirrored the answer to the referenced question fairly strictly, yet when testing this I get an InvalidCastException. In the code below, the lines commented out are the code from the aforementioned answer. I did try running this with the var datatype instead of using double (I dislike var, just a preference), and received the
same error in the same place. I've tried changing the style of cast in various ways, including Convert.ToInt32, (int), and even int.Parse, but everything results in the same error, same location.
Does anybody have a clue as to what the problem with this could be? This is my first real foray into binding like this, so it could be I'm fundamentally misunderstanding it, but honestly don't think that's what it is...
public class AddListRecordsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double result = 0.0;
try
{
double[] doubleValues = values.Cast<double>().ToArray();
foreach (var doubleValue in doubleValues)
{
result += doubleValue;
}
//var doubleValues = values.Cast<double>().ToArray();
//var leftPart = string.Join(" x ", doubleValues);
//var rightPart = doubleValues.Sum().ToString();
//var result = string.Format("{0} = {1}", leftPart, rightPart);
//return result;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Destination TextBox:
<TextBox x:Name="allRecords" Style="{StaticResource dataEntryTextBox}" Grid.Column="1" Grid.Row="6">
<TextBox.Text>
<MultiBinding Converter="{StaticResource AddListRecordsConverter}">
<Binding ElementName="allRecordsOne" Path="Text"></Binding>
<Binding ElementName="allRecordsTwo" Path="Text"></Binding>
</MultiBinding>
</TextBox.Text>
</TextBox>
Source TextBoxes:
<TextBox x:Name="allRecordsOne" Style="{StaticResource dataEntryTextBox}" Grid.Column="0" Grid.Row="4" GotFocus="SelectAllOnFocus_GotFocus" LostFocus="allRecords_LostFocus" />
<TextBox x:Name="allRecordsTwo" Style="{StaticResource readOnlyTextBox}" Grid.Column="0" Grid.Row="5" Text="{Binding ElementName=allRecordsOne, Path=Text}" GotFocus="SelectAllOnFocus_GotFocus" LostFocus="allRecords_LostFocus" />
<TextBox x:Name="allRecordsThree" Style="{StaticResource readOnlyTextBox}" Grid.Column="0" Grid.Row="6" Text="{Binding ElementName=allRecordsOne, Path=Text}" GotFocus="SelectAllOnFocus_GotFocus" LostFocus="allRecords_LostFocus" />
I simplified your example. Note that I used Mode="OneWay" to avoid exception in ConvertBack method.
<StackPanel>
<TextBox x:Name="allRecords">
<TextBox.Text>
<MultiBinding Converter="{StaticResource AddListRecordsConverter}">
<Binding ElementName="allRecordsOne" Path="Text" Mode="OneWay"/>
<Binding ElementName="allRecordsTwo" Path="Text" Mode="OneWay"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
<TextBox x:Name="allRecordsOne" />
<TextBox x:Name="allRecordsTwo" />
</StackPanel>
the issue with the converter is that it receives two empty strings (default value of text) as input (values) and can't handle them properly. it has to be more defensive
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double val = 0.0;
double result = 0.0;
foreach (var txt in values)
{
if (double.TryParse(txt.ToString(), out val))
result += val;
else
return "NaN";
}
return result.ToString();
}

Set the content of Label to Image's Tooltip

I am using IValueconverter interface to change the tooltip text of an image.
The tool tip should change based on label.
<Label Content="9898980001" Height="28" HorizontalAlignment="Left" Margin="1733,231,0,0" Name="lbl02scanning" VerticalAlignment="Top" Foreground="Blue" >
<Image Height="49" HorizontalAlignment="Right" Margin="0,131,113,0"
Name="img02scanning"
Source="/TEST;component/Images/LoadingStation.png" Stretch="Fill"
VerticalAlignment="Top" Width="30" Cursor="Hand">
<Image.ToolTip>
<StackPanel Background="AliceBlue">
<TextBlock Padding="5" Foreground="White" MinHeight="20"
Background="Blue" FontWeight="Bold"
Text="Scanning Station" />
<StackPanel Orientation="Horizontal">
<Image
Source="pack://application:,,,/TEST;component/Images/coilonsaddle_large.png"
Height="100" Width="100" />
<TextBlock Padding="10" TextWrapping="WrapWithOverflow"
MaxWidth="200" Background="AliceBlue"
Foreground="Black" FontWeight="Bold"
Text="{Binding ElementName=lbl02scanning, Path=Name,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
</StackPanel>
<TextBlock Padding="5" Foreground="White" MinHeight="20"
Background="Blue" FontWeight="Bold"
Text="Report to admin in case of coil location mismatch"/>
</StackPanel>
</Image.ToolTip>
</Image>
The converter class:
public class FormatterForCoilToolTip : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(parameter.ToString() == "02")
{
return value.ToString() + " Startin";
}
else
{
return value.ToString() + " Finishing";
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The tooltip's Textblock content is not changing. But if i change to:
Text="{Binding ConverterParameter=255, Converter={StaticResource FormatterFOrCoilToolTip}}
then it is working. But i want to pass the lbl02scanning text value. Why it is not working??
First of all you should bind to Content property and not Name property in case you want Text of Label.
Most importantly Tooltip does not lies in same Visual Tree as that of label, hence binding with elementName won't work. However, you can use x:Reference to get the element even if it doesn't exist in same Visual Tree.
Text="{Binding Source={x:Reference lbl02scanning}, Path=Content,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
Note - x:Reference is introduced in WPF 4.0. If you are using WPF 3.5 you can't use this.
Update for error - service provider is missing the name resolver service
Just found out bug is reported at Microsoft site that x:Reference fails in case Target is Label. However, i couldn't reproduce this issue at my end since i have WPF 4.5 installed at my end and i guess they have fixed the issue in future version.
In case you target WPF 4.0, i would advise you to use TextBlock in place of Label:
<TextBlock Text="9898980001" Height="28" HorizontalAlignment="Left"
Margin="1733,231,0,0" Name="lbl02scanning" VerticalAlignment="Top"
Foreground="Blue" />
and then bind with Text property instead of Content.
Text="{Binding Source={x:Reference lbl02scanning}, Path=Text,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
Either, you can refer to workaround provide under workarounds section here.
You can override the ProvideValue method of the Reference class and skip the reference search login in design time:
[ContentProperty("Name")]
public class Reference : System.Windows.Markup.Reference
{
public Reference()
: base()
{ }
public Reference(string name)
: base(name)
{ }
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget valueTargetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (valueTargetProvider != null)
{
DependencyObject targetObject = valueTargetProvider.TargetObject as DependencyObject;
if (targetObject != null && DesignerProperties.GetIsInDesignMode(targetObject))
{
return null;
}
}
return base.ProvideValue(serviceProvider);
}
Update with another workaround
This will work for all versions WPF 3.5, WPf 4.0 and WPF 4.5.
First of all bind Image Tag with content of label.
Second host your stackPanel inside ToolTip control so that you can
take benefit of PlacementTarget property.
Third bind with PlacementTarget.Tag of Tooltip.
Relevant code will look like this:
<Image Tag="{Binding ElementName=lbl02scanning,Path=Content}">
<Image.ToolTip>
<ToolTip>
<TextBlock Text="{Binding RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=ToolTip},
Path=PlacementTarget.Tag,
ConverterParameter=255,
Converter={StaticResource FormatterFOrCoilToolTip}}"/>
</ToolTip>
</Image.ToolTip>
</Image>
Also you need to update converter code to put null check over there since PlacementTarget will be null until you open tooltip.
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (value != null)
{
if (parameter.ToString() == "02")
{
return value.ToString() + " Starting";
}
else
{
return value.ToString() + " Finishing";
}
}
return String.Empty;
}
Try This
Text="{Binding Path=Content,ElementName=lbl02scanning, ConverterParameter=255, Converter={StaticResource FormatterFOrCoilToolTip}}

Categories