I'm developing a IDataErrorInfo to validate the textboxes I have inside my application. I have the following code:
The .cs class to validate:
public class UserInformation : IDataErrorInfo
{
public string _name;
public string _surname;
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Surname
{
get { return _surname; }
set { _surname = value; }
}
public override string ToString()
{
return Name + " " + Surname;
}
public string this[string columnName]
{
get
{
if (columnName == null) return string.Empty;
string result = string.Empty;
if (columnName.Equals("Name"))
{
if (string.IsNullOrEmpty(_name))
result = "Name cannot be empty.";
}
return result;
}
}
public string Error { get; private set; }
}
The .xaml:
<TextBox Grid.Column="3" Grid.Row="0" Name="TextBoxName"
Style="{DynamicResource InnerTextBox}"
Validation.ErrorTemplate="{StaticResource ValidationErrorTemplate}">
<TextBox.Text>
<Binding Path="Name" Source="{StaticResource UserInformation}"
ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
And the ErrorTemplate:
<ControlTemplate x:Key="ValidationErrorTemplate">
<DockPanel >
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<Grid Width="20" Height="20">
<Ellipse Width="20" Height="20" Fill="Tomato" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Foreground="White" FontWeight="Heavy" FontSize="8" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center"
ToolTip="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">X</TextBlock>
</Grid>
<TextBlock Foreground="Tomato" FontWeight="12" Margin="2,0,0,0" FontSize="20"
Text="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
</StackPanel>
<AdornedElementPlaceholder x:Name="ErrorAdorner" />
</DockPanel>
</ControlTemplate>
The code works fine when I'm typing. But when the TextBox is loaded, the validation occurs too. And I don't want it to happen when it gains focus, only when it looses it or I change the text (like the one published here).
How can I avoid the validation error to be considered on first TextBox load?
NOTE: Even if I set the UpdateSourceTrigger to LostFocus, it is still making the validations.
To acheive you goal you need to:
First, remove ValidatesOnDataErrors="True" on your Binding. As said in docs:
Setting this property provides an alternative to using the
DataErrorValidationRule element explicitly
And we're gonna use it explicitly. Then use DataErrorValidationRule instead of ExceptionValidationRule for correctly working with IDataErrorInfo and data errors.
And last, we need to use some properties that this rule gives us:
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False" />
</Binding.ValidationRules>
ValidatesOnTargetUpdated on false will not trigger validation when target itself changes (i.e. on load). You can also play with ValidationStep property for additional control.
Edit:
Ok, I see that you need to skip validation on load and you need to validate on lost focus even if the value was not changed. Well, validation rules does not support that, because if the value was not updated, then no changed events will be called and no validation will occur, regardless of UpdateSourceTrigger setting.
The easy way out is to emulate this functionality by adding LostFocus handler to TextBox itself:
private void ValidatedTextBox_LostFocus(object sender, RoutedEventArgs e)
{
var txt = (TextBox)sender;
txt.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
If you need this for several TextBoxes, you can move the code to some static class.
The same results can be achieved using Explicit update source trigger, wich can be a little bit more cleaner.
I dont have any example atm, because I moved to what you have. But You will need to create a class which will Inherit from ValidationRule which exist in system.windows.controls, and then override Validate method.
Then your xaml textbox would look something like this instead
<TextBox.Text>
<Binding Path="your binding here" UpdateSourceTrigger="LostFocus" >
<Binding.ValidationRules>
<validationClass:yourRule/> define this at the top of xaml page
</Binding.ValidationRules>
</Binding>
You should be able to find examples on msdn, and here about validation rules
Related
I'm trying to use MultiBinding to bind a slider to a textbox (which works) and bind the TextBox to a property (which doesn't work). The TextBox/Property binding works fine with single binding, but when I introduce MultiBinding, it breaks.
Here's my XAML
<Slider
Name="SliderExportQuality"
Value="100"
Minimum="0"
Maximum="100"
HorizontalAlignment="Left"
Margin="10,5,0,0"
VerticalAlignment="Top"
Width="239"/>
<TextBox>
<TextBox.Text>
<MultiBinding StringFormat="N2">
<Binding ElementName="SliderExportQuality" Path="Value"/>
<Binding Path="ExportQuality" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
Here's the dialog box. The TextBox is trimmed with red after I try entering a value directly into it, which is telling me something's wrong?
I read up a bit on MultiBinding and think I may be going awry with my Converter but am clueless with what it should be.
As others have mentioned in the comments, unless I am misunderstanding the question, you should not need a multibinding to accomplish what you are trying to do. To get the slider value to display in the text box ( and the other way around ) you just need to bind the value to a common property in your view model.
For example, given the following xaml:
<Grid Margin="20" VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Slider Grid.Column="0" Minimum="0" Maximum="100" Value="{Binding FileSize}"/>
<TextBox Grid.Column="1" Text="{Binding FileSize}"/>
</Grid>
You have a slider which binds its value to the FileSize property in your view model.
The associated ViewModel:
class MainWindowViewModel : INotifyPropertyChanged
{
public int FileSize
{
get
{
return mFileSize;
}
set
{
if(mFileSize != value)
{
mFileSize = value;
OnPropertyChanged(nameof(FileSize));
}
}
} private int mFileSize = 50;
private void OnPropertyChanged(String propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
This allows the value to be changed by either the slider, or by typing directly into the text box. There still needs to be error checking on the text box as a user could type in anything... But this shows the basic concept.
This produces the following UI.
I hope that addresses the question you were asking.
When you use that Binding it will try to set the "75" (notice it's string) and value of ExportQuality (don't know the type of this though) on the Slider.Value property, which is double, it has red border because the type is wrong (i.e. TextBox is trying to use incorrect type for the Binding, this also happens if you Bind TextBox to an int property and you type in "a"), should you use a converter this is the case when it will ConvertBack();
FYI MultiValueConverter would be used in this case.
What I think you wanted to use here was PriorityBinding. Which would be used like this:
<TextBox>
<TextBox.Text>
<PriorityBinding>
<Binding ElementName="SliderExportQuality" Path="Value"/>
<Binding Path="ExportQuality" UpdateSourceTrigger="PropertyChanged"/>
</PriorityBinding>
</TextBox.Text>
</TextBox>
This way if the first Binding fails the second will kick in.
I want to reuse an XAML fragment with substitution strings as parameters.
sort of like a #define with some function style arguments.
Can I do this?
If so, how is the best way to go around doing it?
So, here's invalid XAML of what I want to do
<Template Base="TextBox" key="ValidatedTextBox">
<TextBox.Text>
<Binding NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged" Path="{SomeAttributeName}">
<Binding.ValidationRules>
<local:SomeRule></local:SomeRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</Template>
...
and then elsewhere in XAML, instead of using a TextBox, I'd do
<ValidatedTextBox SomeAttributeName="MyPropertyToBeBound" AttributeNotOnTemplate="Value">
<ElementNotOnTemplate />
</ValidatedTextBox>
In particular, I want to be able to customize instances of this template.
I'm happy to read docs, but I don't know what to search for to find appropriate docs that aren't hilariously complicated for essentially a find-and-replace mechanism.
I'm not sure whether you're just trying to add validation to a TextBox or create a custom control. If you're struggling with the validation this post gives a very nice overview. In summary, you can do something like this
public class ViewModel : System.ComponentModel.IDataErrorInfo
{
public ViewModel()
{
/* Set default age */
this.Age = 30;
}
public int Age { get; set; }
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "Age":
if (this.Age < 10 || this.Age > 100)
return "The age must be between 10 and 100";
break;
}
return string.Empty;
}
}
}
And then use it like this
<TextBox Text="{Binding Path=Age, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
If you'd like to create your own error template you can do it for an individual TextBox like this
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<!-- Placeholder for the TextBox itself -->
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
Alternatively, you can define a style like this
<Style TargetType="x:TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If you're trying to create a custom control this post and this question are worth looking at. The key part is adding a DependencyProperty to your control like this
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyTextBox), new PropertyMetadata(string.Empty, OnTextChangedCallback));
private static void OnTextChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Validation in here
}
Then you could use it like this
<MyTextBox Text="My Text" .../>
I am working on a wpf mvvm project. In a user control I have a datagridControl from Devexpress that is bound to data from a Observable collection.
<xcdg:DataGridControl x:Name="DataGridName" HorizontalAlignment="left" VerticalAlignment="Stretch"
AutoCreateColumns="False"
ItemsSource="{Binding ViewModel.Items}"
ItemScrollingBehavior="Immediate" SynchronizeCurrent="True" TabIndex="69" >
<xcdg:DataGridControl.Columns >
<xcdg:Column FieldName="Name" AllowSort="False" Title="Name" ShowInColumnChooser="False" />
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
The class in the Observable collection Contains a Name (string) and IsVerified (Boolean).
private ObservableCollection<myData> _items = new ObservableCollection<myData>();
public ObservableCollection<myData> Items
{
get { return _items; }
set { _items = value; }
}
public class myData
{
public string Name { get; set; }
public bool IsVerfied { get; set; }
}
I also have a textblock that I use to display an error message above the dataGrid when the Value of IsVerfied is false.
<TextBlock Name="textBlockErrrMessage" Foreground="IndianRed">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MultiValueConverter}">
<Binding Path="DataContext.IsVerified" RelativeSource="{RelativeSource AncestorType=xcdg:DataRow}" ElementName="DataGridName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
To do this I plan on having a multivalueconverter (I am also doing the same thing but for a different control so that is why I choose a MultiValueConverter) that I would like to send the IsVerfied value from the Collection and return the message. My issue is how do I set the Binding in the MultiBinding to read the IsVerfied value from the Observablecollection. This particular line is what I believe is the issue in locating the Collection value
<Binding
Path="DataContext.IsVerified"
RelativeSource="{RelativeSource AncestorType=xcdg:DataRow}"
ElementName="DataGridName" />
In your Binding, you want to use either RelativeSource or ElementName, but not both. See this post for a good clarification on the differences between the two.
I have 3 TextBoxes bind with my class(Transaction) properties like this
<TextBox Text="{Binding Path=Transaction.Bills100,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills100" Grid.Column="2" Grid.Row="1" Margin="7"></TextBox>
<TextBox Text="{Binding Path=Transaction.Bill50,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills50" Grid.Column="2" Grid.Row="2" Margin="7"></TextBox>
<TextBox Text="{Binding Path=Transaction.Bill20,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills20" Grid.Column="2" Grid.Row="3" Margin="7"></TextBox>
Also I have another TextBox where I have done multibinding and done addition of the first three Textboxes like
<TextBox Grid.Column="2" IsReadOnly="True" Grid.Row="7" Grid.ColumnSpan="2" Margin="7" Name="TotalBills">
<TextBox.Text>
<MultiBinding Converter="{ikriv:MathConverter}" ConverterParameter="x+y+z" Mode="TwoWay">
<Binding Path="Text" ElementName="bills100" />
<Binding Path="Text" ElementName="bills50" />
<Binding Path="Text" ElementName="bills20" />
</MultiBinding>
</TextBox.Text>
</TextBox>
I want to bind this multibinding textbox with my class(Transaction) with property as Transaction.Total like my first three textboxes but it shows error
Property text is set more than once
Actually we cannot get the value of a two-way binding from one property and then set the value of another property.
Finally I came with a solution like this
In my Class Transaction
private double _totalBills;
public double TotalBills
{
get { return _totalBills; }
set { _totalBills= value; Notify("TotalBills"); }
}
In XAML(Instead of Multibinding)
<TextBox Text="{Binding Path=Transaction.TotalBills,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Column="2" IsReadOnly="True" Grid.Row="7" Grid.ColumnSpan="2" Margin="7" Name="TotalBills"/>
My ViewModel
public class MainViewModel: INotifyPropertyChanged
{
private Transaction _transactionDetails;
public MainViewModel()
{
Transaction= new Transaction();
_transactionDetails.PropertyChanged += _transactionDetails_PropertyChanged;
}
private void _transactionDetails_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "TotalBills":
_calculate(); //My method for calculation
break;
}
}
}
I have a textbox, which uses multi-binding usingStringFormat...as shown below.
But it displays the default value as
{DependencyProperty.UnsetValue},{DependencyProperty.UnsetValue}
How to avoid this ?
<StackPanel Orientation="Horizontal">
<TextBlock Width="70" Text="Name:" Margin="5,2,2,2"></TextBlock>
<TextBox Width="160" DataContext="{Binding }" IsReadOnly="True" Margin="2">
<TextBox.Text>
<MultiBinding StringFormat="{}{0},{1}">
<Binding Path="LastName"/>
<Binding Path="FirstName"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
Please help me.
Something is wrong with the object you are binding to. I just created an application from scratch with a Person class that inherits from DependencyObject. I left the first and last name properties unset and I did not see DependencyProperty.UnsetValue, but rather a blank TextBox with just a comma in it.
(In general, you shouldn't use dependency properties on your business objects anyway. Stick to INotifyPropertyChanged and save yourself a ton of headaches.)
Post the code to your bound object and maybe I can spot the issue.
public class Person : DependencyObject
{
public static readonly DependencyProperty FirstNameProperty = DependencyProperty.Register("FirstName", typeof(string), typeof(Person), new FrameworkPropertyMetadata());
public string FirstName {
get { return (string)GetValue(FirstNameProperty); }
set { SetValue(FirstNameProperty, value); }
}
public static readonly DependencyProperty LastNameProperty = DependencyProperty.Register("LastName", typeof(string), typeof(Person), new FrameworkPropertyMetadata());
public string LastName {
get { return (string)GetValue(LastNameProperty); }
set { SetValue(LastNameProperty, value); }
}
}
-
<TextBox IsReadOnly="True">
<TextBox.Text>
<MultiBinding StringFormat="{}{1}, {0}">
<Binding Path="FirstName" />
<Binding Path="LastName" />
</MultiBinding>
</TextBox.Text>
</TextBox>
-
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
private void Window_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
var p = new Person();
//p.FirstName = "Josh";
//p.LastName = "Einstein";
DataContext = p;
}
}
just give the fallbackvalue="" and see
<TextBox.Text>
<MultiBinding StringFormat="{}{0},{1}">
<Binding Path="LastName" FallbackValue=""/>
<Binding Path="FirstName" FallbackValue=""/>
</MultiBinding>
</TextBox.Text>
If a binding is not successful, i.e. the path to the binding source is not found or the value converter , if any, fails, a DependencyProperty.UnsetValue is returned, then the target property is set to the FallbackValue, if you defined one of course.