Bind ComboBox Text to a ValidationRule inside another ComboBox? - c#

I would like to bind the text from ComboBox CB1 to the Validation Rule (CommunicationMode) in ComboBox CB2.
My source code looks like this, but I get the error: "Binding" can only be set for a "DependencyProperty" of a "DependencyObject".
Is there a way to solve this?
public string CommunicationMode { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
return new ValidationResult(true, null);
}
<ComboBox Name="CB1">
<ComboBox.Text>
<Binding Path="CB1" UpdateSourceTrigger="PropertyChanged"/>
</ComboBox.Text>
</ComboBox>
<ComboBox Name="CB2">
<ComboBox.Text>
<Binding Path="CB2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<valid:ComboboxValidationRule CommunicationMode="{Binding ElementName=CB1, Path=Name}" ValidatesOnTargetUpdated="True"/>
</Binding.ValidationRules>
</Binding>
</ComboBox.Text>
</ComboBox>

You could create a Wrapper class with a dependency property:
public class ComboboxValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
//your validation logic...
return new ValidationResult(true, null);
}
public Wrapper Wrapper { get; set; }
}
public class Wrapper : DependencyObject
{
public static readonly DependencyProperty CommunicationModeProperty =
DependencyProperty.Register(nameof(CommunicationMode), typeof(string), typeof(Wrapper));
public string CommunicationMode
{
get { return (string)GetValue(CommunicationModeProperty); }
set { SetValue(CommunicationModeProperty, value); }
}
}
XAML:
<ComboBox Name="CB2">
<ComboBox.Text>
<Binding Path="CB2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<valid:ComboboxValidationRule ValidatesOnTargetUpdated="True">
<valid:ComboboxValidationRule.Wrapper>
<valid:Wrapper CommunicationMode="{Binding Source={x:Reference CB1}, Path=Name}" />
</valid:ComboboxValidationRule.Wrapper>
</valid:ComboboxValidationRule>
</Binding.ValidationRules>
</Binding>
</ComboBox.Text>
</ComboBox>

Related

Bind custom class with Validation in WPF

I noticed that it is possible to bind variables of the type DateTime to a textbox in WPF. If I enter a wrong value it will not validate and show a red border.
How can I implement my own class, that I can bind to a textbox without having to bind to a property of the class? The Textbox should show a string and the class will validate the input.
Is this possible?
My current solution is this:
In the Model:
public string DefaultLanguageValue
{
get
{
return _defaultLanguageValue;
}
set
{
if (value != this._defaultLanguageValue)
{
ValidateLanguage(value);
this._defaultLanguageValue = value;
NotifyPropertyChanged();
}
}
}
private void ValidateLanguage(string value)
{
string rx = "([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*";
if (!Regex.IsMatch(value, rx))
{
throw new ArgumentException();
}
}
In the XAML:
<TextBox Text="{Binding TreeViewModel.Model.DefaultLanguageValue, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}" BorderThickness="0" MinWidth="100"/>
It would be nice to have a Class that I can just bind like a String, Int or DateTime for examlpe. Any Ideas?
You could bind to the Tag property of the TextBox itself and validate using a ValidationRule:
public class DateValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (!DateTime.TryParse(value as string, out DateTime _))
return new ValidationResult(false, "Invalid date...");
return ValidationResult.ValidResult;
}
}
XAML:
<TextBox>
<TextBox.Text>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DateValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
This doesn't require you to bind to a view model.
I finally tried the solution suggested by mm8.
The only issue I have now is that if one enters an invalid value into the textbox, it will not update the textbox when I programmatically change the value of the source after clicking a button.
I tried Validation after update, but this allows the user to save invalid values.
<TreeViewItem>
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chkDefaultLanguage" IsChecked="{Binding TreeViewModel.TreeModel.DefaultLanguage, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="DefaultLanguage: " />
<TextBox BorderThickness="0" MinWidth="100">
<TextBox.Text>
<Binding Path="TreeViewModel.TreeModel.DefaultLanguageValue" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validationrules:LanguageCodeValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
class LanguageCodeValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string rx = "([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*";
if (!Regex.IsMatch(value.ToString(), rx))
{
return new ValidationResult(false, "Invalid Language Codee.");
}
return ValidationResult.ValidResult;
}
}

After validation a field, property has last valid value

In my WPF project i started use ValidationRule with text fields and found a problem. I created simple project to test ValidationRule. Everything works fine, but if my input value is not in valid range, my property store last valid value. Thats why i have a question: how can I check current value, that not valid for ValidationRule? May be, i doing something wrong?
Main Window
</Window ... >
<Window.Resources>
<ControlTemplate x:Key="errorTemplate">
<Border BorderBrush="OrangeRed" BorderThickness="2">
<Grid>
<AdornedElementPlaceholder/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="OrangeRed"
VerticalAlignment="Center" HorizontalAlignment="Right"
Margin="0,0,4,0"/>
</Grid>
</Border>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<TextBox Validation.ErrorTemplate ="{StaticResource errorTemplate}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Width="200" Margin="0 20">
<TextBox.Text>
<Binding Path="ForText"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:EmptyFieldRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button Content="Check"
Command="{Binding ForCommand}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="40"/>
</StackPanel>
</Window>
View model for Window, that i set in DataContext:
public class MainWindowVM : VM
{
private string _text = "some text";
public string ForText
{
get => _text;
set { _text = value; OnPropertyChanged(nameof(ForText)); }
}
public ICommand ForCommand { get; set; }
public MainWindowVM() { ForCommand = new RelayCommand(() => MessageBox.Show(ForText)); }
}
Validation rule:
public class EmptyFieldRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string information = value as string;
if (string.IsNullOrWhiteSpace(information))
return new ValidationResult(false, "!!!");
return ValidationResult.ValidResult;
}
}
Binding in wpf evaluate all the ValidationRule before updating the value. You have to make few changes in the EmptyFieldRule class to achieve this. This is a kind of trick to resolve your issue but there are other better ways to implement the same.
Change ValidationStep property to ValidationStep.UpdatedValue and use Reflection to get updated value in the ValidationResult method.
public class EmptyFieldRule : ValidationRule
{
public EmptyFieldRule()
{
this.ValidationStep = ValidationStep.UpdatedValue;
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var expression = value as BindingExpression;
var information = expression?.DataItem?.GetType()?.GetProperty(expression.ResolvedSourcePropertyName)?.GetValue(expression.DataItem) as string;
if (string.IsNullOrWhiteSpace(information))
return new ValidationResult(false, "!!!");
return ValidationResult.ValidResult;
}
}

Binding to different property of bound object in Validation Rule

Given the following View Model example
public class MyViewModel
{
public ObservableCollection<MyObjType> BoundItems { get; }
}
and MyObjType
public class MyObjType
{
public string Name { get; set; }
public int Id { get; set; }
}
I have added a Validation rule to a DataGrid Column, where the DataGrid is bound to the BoundItems collection in my ViewModel, and the Text property in the Template Column is bound to the Name.
<DataGrid ItemsSource="{Binding BoundItems}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TexBox>
<TextBox.Text>
<Binding Path="Name" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<xns:MyValidationRule>
<xns:MyValidationRule.SomeDependencyProp>
<xns:SomeDependencyProp SubProp={Binding Id} /> <!-- Not Working -->
</xns:MyValidationRule.SomeDependencyProp>
</xns:MyValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
...
</DataGrid.Columns>
</DataGrid>
I want to pass another property Id of my collection type (MyObjType) to the validation rule, how do I access that from the rule. I know about the freezable and getting the context of the view model, but i need another property of my collection type that is bound to the Datagrid.
The ValidationRule and SomeDependencyProp is modeled after the example here: https://social.technet.microsoft.com/wiki/contents/articles/31422.wpf-passing-a-data-bound-value-to-a-validation-rule.aspx
public class SomeDependencyProp : DependencyObject
{
public static readonly SubPropProperty =
DependencyProperty.Register("SubProp", typeof(int),
typeof(SomeDependencyProp), new FrameworkPropertyMetadata(0));
public int SubProp{
get { return (int)GetValue(SubPropProperty ); }
set { SetValue(SubPropProperty, value); }
}
}
public class MyValidationRule: System.Windows.Controls.ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
...
}
public SomeDependencyProp SomeDependencyProp { get; set; }
}
The solution to this situation is to use a BindingProxy.

WPF Label content shows DependencyProperty.UnsetValue in design mode

I have an WPF label and I have bound some data into a string using StringFormat from xaml:
<Label Grid.Row="0" Grid.Column="1" Style="{StaticResource MyLblResource}">
<Label.Content>
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat="{}({0}) {1}">
<Binding Path="MyDataModel.Id" />
<Binding Path="MyDataModel.Desc" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Label.Content>
</Label>
Above code works fine, no problems but in design time, in xaml view, in the TextBlock content it is shown:
{{DependecyProperty.UnsetValue}} {{DependencyProperty.UnsetValue}}
Why is this being shown instead of being shown as empty? Is there any way to show this as empty?
This should do the trick:
public class StringFormatConverter : MarkupExtension, IMultiValueConverter
{
public string StringFormat { get; set; } = #"({0}) {1}";
public string PlaceHolder { get; set; } = "Empty";
public override object ProvideValue(IServiceProvider serviceProvider) => this;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return string.Format(StringFormat, GetValues(values));
}
private IEnumerable<string> GetValues(object[] values)
{
foreach (var value in values)
yield return value == DependencyProperty.UnsetValue || value == null ? PlaceHolder : value.ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new[] { Binding.DoNothing, Binding.DoNothing };
}
}
Use it like this:
<MultiBinding Converter="{converter:StringFormatConverter PlaceHolder=MyPlaceHolderText}">
<Binding Path="MyDataModel.Id" />
<Binding Path="MyDataModel.Desc" />
</MultiBinding>
Please be aware that you can only set static values in StringFormat and PlaceHolder - because they are no DependencyProperty.

error in creating an instance of class in xaml and setting its properties

i need to create an instance of a class with two property that would be used as converter parameter of a binding.
the class is as below:
public class UnitQuantityBindClass:DependencyObject
{
public static readonly DependencyProperty QuantityProperty = DependencyProperty.Register(
"Quantity", typeof(EQuantities), typeof(UnitQuantityBindClass));
public EQuantities Quantity
{
get
{
return (EQuantities) GetValue(QuantityProperty);
}
set { SetValue(QuantityProperty, value); }
}
public static readonly DependencyProperty UnitProperty = DependencyProperty.Register(
"Unit", typeof(Enum), typeof(UnitQuantityBindClass));
public Enum Unit
{
get { return (Enum)GetValue(UnitProperty); }
set { SetValue(UnitProperty, value); }
}
}
the xaml code is as below also:
<textboxunitconvertor:TextBoxUnitConvertor Name="gasDensityValueControl" InstantaneousConvert="True" Margin="96,163,0,0" IsEnabled="{Binding ElementName=chkGas,Path=IsChecked}" QuantityBind="{Binding _FluidBlackOilClass.SGGas_SC.Quantity , RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Width="206" Height="28" HorizontalAlignment="Left" VerticalAlignment="Top">
<textboxunitconvertor:TextBoxUnitConvertor.TextBoxText>
<Binding Path="_FluidBlackOilClass.SGGas_SC.Value" RelativeSource="{RelativeSource AncestorType=Window}" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" Converter="{StaticResource ValueStorageForUnitConverter}">
<Binding.ConverterParameter>
<classes:UnitQuantityBindClass Quantity="{Binding ElementName=gasDensityValueControl,Converter={StaticResource DummyConverter} ,Path=_Quantity,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, PresentationTraceSources.TraceLevel=High}" Unit="{Binding ElementName=gasDensityValueControl,Path=_CurrentUnitEnum,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></classes:UnitQuantityBindClass>
</Binding.ConverterParameter>
</Binding>
</textboxunitconvertor:TextBoxUnitConvertor.TextBoxText>
<textboxunitconvertor:TextBoxUnitConvertor.CurrentUnitEnumBind>
<Binding Source="{StaticResource CurrentFlowProWorkingClass}" Path="currentFlowProWorkingClass.ProjectUnitSystem" UpdateSourceTrigger="PropertyChanged" Mode="OneTime">
<Binding.ConverterParameter>
<classes:UnitQuantityBindClass Quantity="{Binding ElementName=gasDensityValueControl,Path=_Quantity,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Unit="{Binding ElementName=gasDensityValueControl,Path=_CurrentUnitEnum,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></classes:UnitQuantityBindClass>
</Binding.ConverterParameter>
</Binding>
</textboxunitconvertor:TextBoxUnitConvertor.CurrentUnitEnumBind>
</textboxunitconvertor:TextBoxUnitConvertor>
but the create class is empty and the Unit and quantity properties does not bind.
why?

Categories

Resources