I was wondering if there is way to bind multiple textboxes to a single property in VM and a single validation logic. Perhaps a good example of that would be a typical Serial Number in any product activation process. Usually, when asked to enter serial number, the end user has, say, 5 text boxes with 5 max symbols.
Now lets imagine that in my VM I have only one property called SerialNumber. So my XAML would look like this:
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<StackPanel>
And my code will be like this:
class ViewModel
{
public string SerialNumber{get;set;}
}
Is there a way to bind these textboxes so that they each point ot the same property on VM and that property is updated only when the validation on all 5 textboxes passes?
EDIT: As some posters pointed out, yes, I could go with 5 separate properties for each textbox, the problem with that is that the actual situation is much more complex than in the example that I've provided. One of the reasons that the suggested approach is unfavorable is because this view will be reused in multiple places with different VM classes and going with the 5 properties approach I would have to copy them in each and every VM class that will use this View. If it were only as simple as taking five string properties and concatenating them, it would be tolerable. But in the real world scenario there is a very complicated verification, validation and combination logic behind these properties, that it makes it impractical to rewrite the same logic in each and every VM, which is why I'm looking for something reusable, something that could be done in XAML as much as possible. I was wondering if BindingGroup with some sort of ValidationRule and ValueConverter could be used in this case.
You should try to bind it into separate properties and then use the + operator to link them togeather, in a separate property and use that after in the model.
You cannot data bind different UI controls to one property and have them reflect different values. Instead, you will need to define additional properties to data bind to the other TextBoxes. It's stil a bit unclear as to exactly what you want, but if you want several TextBoxes that each show a few characters of a pass code, or something similar, then you'll need to do it more like this:
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber1}" />
<TextBox Text="{Binding SerialNumber2}" />
<TextBox Text="{Binding SerialNumber3}" />
<TextBox Text="{Binding SerialNumber4}" />
<TextBox Text="{Binding SerialNumber5}" />
<StackPanel>
...
string serialNumber = string.Concat(SerialNumber1, SerialNumber2, SerialNumber3,
SerialNumber4, SerialNumber5);
Alternatively, if you want to compare the supposedly identical values of two TextBoxes, as in a typical password entry field, you could do something like this:
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber1}" />
<TextBox Text="{Binding SerialNumber2}" />
<StackPanel>
...
bool isValid = SerialNumber1 == SerialNumber2;
In all cases, you will need to add further properties.
Based on the edit of the question, it is impractical for the O/P to change the viewmodel(s) to add additional string properties for the serial number parts.
A custom IValueConverter
In this situation, a custom IValueConverter can provide the required functionality. Let's call this custom converter SerialNumberConverter.
As already hinted at by IL_Agent's very brief answer, you would use the converter in XAML simliar to the following:
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<My:SerialNumberConverter x:Key="SerialNumberConverter" />
</StackPanel.Resources>
<TextBox Text="{Binding SerialNumber, ConverterParameter=0, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=1, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=2, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=3, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=4, Converter={StaticResource SerialNumberConverter}}"/>
</StackPanel>
The implementation of the SerialNumberConverter looks somewhat unconventional:
public class SerialNumberConverter : IValueConverter
{
private readonly string[] _serialNumberParts = new string[5];
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int serialPartIndex;
if (!int.TryParse(parameter.ToString(), out serialPartIndex)
|| serialPartIndex < 0
|| serialPartIndex >= _serialNumberParts.Length
)
return Binding.DoNothing;
string completeSerialNumber = (string) value;
if (string.IsNullOrEmpty(completeSerialNumber))
{
for (int i = 0; i < _serialNumberParts.Length; ++i)
_serialNumberParts[i] = null;
return "";
}
_serialNumberParts[serialPartIndex] = completeSerialNumber.Substring(serialPartIndex * 6, 5);
return _serialNumberParts[serialPartIndex];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int serialPartIndex;
if (!int.TryParse(parameter.ToString(), out serialPartIndex)
|| serialPartIndex < 0
|| serialPartIndex >= _serialNumberParts.Length
)
return Binding.DoNothing;
_serialNumberParts[serialPartIndex] = (string)value;
return (_serialNumberParts.Any(string.IsNullOrEmpty)) ?
Binding.DoNothing :
string.Join("-", _serialNumberParts);
}
}
How does it work? For the following explanation, the reader is required to have a basic understanding of how the binding mechanism of WPF utilizes IValueConverters.
Convert method
First, let's take a look at the Convert method. The value passed to that method is obviously coming from the view models SerialNumber property and thus is a complete serial number.
Based on the ConverterParameter - which specifies the serial number part to be used for a particular binding - the appropriate portion of the serial number string will be extracted. In the example converter given here, i assumed a serial number format of five parts with 5 characters each, and each part being separated from another by a hyphen - character (i.e., a serial number would look like "11111-22222-33333-44444-55555").
Obviously the Convert method will return this serial number part, but before doing so it will memorize it in a private string array _serialNumberParts. The reason for doing this becomes clear when looking at the ConvertBack method.
Another responsibility of the Convert method is erasing the _serialNumberParts array in case the bound SerialNumber property provides an empty string or null.
ConvertBack method
The ConvertBack method essentially converts the data from the text box before it is being assigned to the SerialNumber property of the view model. However, the text box will only provide one part of the serial number -- but the SerialNumber property needs to receive a complete serial number.
To create a complete serial number, ConvertBack relies on the serial number parts memorized in the _serialNumberParts array. However, before composing the complete serial number the _serialNumberParts array will be updated with the new data provided by the text box.
In case your UI starts with empty text boxes, the ConvertBack method will not return a serial number until all text boxes have provided their data (i.e., until the user has typed something into all text boxes). Instead, the method will return Binding.DoNothing in case a complete serial number cannot be composed yet. (Binding.DoNothing instructs the binding to do (erm...) nothing.)
Considerations regarding SerialNumberConverter
For this converter to work without troubles, the following considerations need to be taken into account:
The bindings of each text box belonging to the same serial number need to use the same converter instance (so that the _serialNumberParts array will be able to keep track of the complete serial number)
If the UI provides several text box groups for entering multiple serial numbers, then each of these text box groups need to use a separate converter instance (otherwise, serial number parts of different serial numbers could mix in the same _serialNumberParts array). In my XAML example above, i ensured this by placing the converter instance in the resource dictionary of the parent StackPanel of the text boxes (which makes the converter instance local to this StackPanel and its descendent elements).
It is required to use data bindings for all serial number parts. Otherwise, the _serialNumberParts array will never be populated fully, which in turn will prevent ConvertBack from returning any complete serial number.
A ValidationRule for the text boxes
If validation of input should be handled for each text box individually, a custom ValidationRule is required:
public class SerialNumberValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string serialNumberPart = value.ToString();
return (serialNumberPart.All(c => '0' <= c && c <= '9')) ?
(serialNumberPart.Length == 5) ?
ValidationResult.ValidResult :
new ValidationResult(false, "Serial number part must be 5 numbers") :
new ValidationResult(false, "Invalid characters in serial number part");
}
}
In the example SerialNumberValidationRule given here i assume that only number characters are valid characters for a serial number (you would of course implement the ValidationRule differently depending on the specification of your serial number format...)
While implementing such a ValidationRule is rather easy and straightforward, attaching it to the data bindings in XAML is unfortunately not as elegant:
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<My:SerialNumberConverter x:Key="SerialNumberConverter" />
</StackPanel.Resources>
<TextBox>
<TextBox.Text>
<Binding Path="SerialNumber" ConverterParameter="0" Converter="{StaticResource SerialNumberConverter}">
<Binding.ValidationRules>
<My:SerialNumberValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox>
<TextBox.Text>
<Binding Path="SerialNumber" ConverterParameter="1" Converter="{StaticResource SerialNumberConverter}">
<Binding.ValidationRules>
<My:SerialNumberValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
...here follow the remaining text boxes...
</StackPanel>
The reason for this convoluted XAML is Binding.ValidationRules being a read-only property. Unfortunately that means we cannot simply write something like
<Binding ValidationRules="{StaticResource MyValidator}" ... />
but instead need to resort to this kind of verbose XAML shown above to add our SerialNumberValidationRule to the Binding.ValidationRules collection.
Final notes
For the sake of readability, i omitted any sanity checks in my example converter code which are not required to get an understanding of how the code works. Depending on your requirements and application scenario you might need to add sanity checks to the converter code to prevent it from going haywire if the view model's SerialNumber property could possibly provide improper data.
The validation as depicted above will just show a slim red rectangle around a text box if the ValidationRule fails (this is default behavior for a text box). If your UI should present a more elaborate validation error response, most certainly you will need to do much more than just only adding the ValidationRule to the bindings...
I think the best way is using of 5 separate properties, but if you want only one you could use Converter and pass order number of each part as parameter.
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=0}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=1}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=2}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=3}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=4}"/>
<StackPanel>
Related
I am new to wpf and aside from the reading of 'WPF unleashed', I try to write a little program, it gives me a direction to go to.
So here is my problem : I want to manage my books, at home, with a little library program. Each book is stored in a Book class instance. thus, This Book class contains all the informations of the book, and among them its keywords, which is a ObservableCollection in a 'Keywords' property:
public ObservableCollection<string> Keywords
{
get => _keywords;
set
{
_keywords = value;
OnPropertyChanged("Keywords");
}
}
(_keywords is a string).
What I want is to display a textBox and a combobox which show related piece of informations: the textBox show all the Keywords of the selected book, and the combobox show a list of checkboxes, it is filled by the list of all the Keywords in all the books of the library, listed once (duplicate removed) and each checkbox is checked if the keyword is present in the listbox of the selected book.
I will try to explain it differently : each book contains a list of all its keywords. The combobox contains all the existing keywords (of all the books) and those related to the selected book are checked : it allows me 2 ways to add/edit/remove keywords : with the textbox, or by checking/unchecking the checkBoxes.
Back to my problem : how can I code this, using a maximum of databinding?
For the textbox, I see no problem : I will create a property in 'Book' say 'KeywordsForTextbox' with only a getter returning the keywords collection items merged with the appropriate separation character. then, using a dataContext pointed to the selectedBook, the text property of the textbox is bound to KeywordsForTextbox.
I would rather not considering the textbox edition for now.
But for the combobox, there are 2 things to bind:
1/ the list of all the keywords. I can put in my BookManagement class (which ... manages the Book class) a property whose getter will return the list of all the keywords (via link).
2/ each ComboboxItem will be edited in XAML but how to implement the behavior of setting it as checked/unchecked if the given keyword(got by using the property ItemSource of the checkbox to the Keyword property of the Book instance) is contained in the selectedItem.Keywords ?
more shortly : how to bind the checked property of a CheckBoxItem to the presence of this string in listView's selectedItem.Keywords (a list of strings)?
I apologize for the messy aspect of my question!
thank you.
EDIT
Well, I managed to write all the stuff, it builds and run, but there is a problem; in fact in the combobox, the itemsSource was written like this:
ItemsSource="{Binding Source={StaticResource AllBooks}}"
and AllBooks is a resource:
<ObjectDataProvider
x:Key="AllBooks"
MethodName="listOfAllKeywords"
ObjectType="{x:Type mangmt:BookManagement}" />
here is the method:
public static ObservableCollection<string> listOfAllKeywords()
{
if (App.Books != null)
{
IEnumerable<string> kws = App.Books.SelectMany(book => book.Keywords).Distinct();
return new ObservableCollection<string>(kws);
}
else return new ObservableCollection<string>();
}
When I run my program, the window is displayed normally, but if I click on a book to display it, and if I click on the combobox, at the second click I get an exception :
System.InvalidCastException : 'Impossible d'effectuer un cast d'un objet de type 'MS.Internal.NamedObject' en type 'System.String'.'
which saying the cast is impossible.
I saw in the debugging that in the multibinding, the seconf parameter is null(anyway it seems not very relevant because the exception is caused by the first parameter, but yet it's annoying).
I recall you my multibinding:
<ComboBox
x:Name="cbb_Keywords"
Grid.Column="2"
Width="300"
Margin="5,0,0,0"
HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource AllBooks}}"
DataContext="{Binding ElementName=listBoxBooks,Path=SelectedItem,Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="200">
<MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}" >
<Binding Path="KeywordsForTextbox"></Binding>
<Binding RelativeSource="{RelativeSource Self}" Path="Content"></Binding>
</MultiBinding>
</CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
here is KeywordsForTextBox:
public string KeywordsForTextbox
{
get { return string.Join(",", _keywords); }
}
(it's in the Book class)
is the first binding (of the multibinding) wrong?why?
thank you.
EDIT 2
I created a new post because this one is too messy(too much text in my question) and the area of the possible causes is much restricted as I saw this was a dependencyProperty problem. here is the link : here in stack overflow
based on my suggestion in the comment, IMultiValueConverter (Sorry in VB):
Public Class TextInListTrueFalseConverter
Implements IMultiValueConverter
Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
Dim Checked As Boolean = False
If Not values Is Nothing Then
If values.Count = 2 Then
Dim ListString As String = values(0)
Dim WordToFind As String = values(1)
If Not ListString Is Nothing Then
Dim KeywordList As List(Of String) = ListString.Split(";").ToList 'Assuming you seperator is a ;
If KeywordList.Contains(WordToFind) Then Checked = True
End If
End If
End If
Return Checked
End Function
Public Function ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
XAML:
<CheckBox >
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}">
<Binding Path="KeywordsForTextbox" />
<Binding RelativeSource="{RelativeSource Self}" Path="Content" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
(use DataContext of the Combo to bind to the Book class, and use ItemsSource to load the KeyWords)
Finally, add <Local:TextInListTrueFalseConverter x:Key="TextInListTrueFalseConverter" /> as a resource, adding the "Local" namespace if not already present.e.g. xmlns:Local="clr-namespace:APPNAME".
Not tested, but think it should work, or at least give you something to go on.
EDIT
My bad. I had it in my head that the ComboBoxItems would Inherit the DataContext from the ComboBox, but of course they do not - they are bound to the ItemsSource obviously. Try the following changes, I have updated the first Binding to be to the ListBox of Books. I have also added in the <CheckBox.IsChecked> where appropriate, and also added Content="{Binding}" to the CheckBox:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="200" Content={Binding}>
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}" >
<Binding ElementName=listBoxBooks, Path=SelectedItem.KeywordsForTextbox"></Binding>
<Binding RelativeSource="{RelativeSource Self}" Path="Content"></Binding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
You may also wish to add some validation to the IMultiValueConverter to make sure the passed values are not unset, to avoid an exception: If Not values(0) Is DependencyProperty.UnsetValue And Not values(1) Is DependencyProperty.UnsetValue Then in VB.
I am using a PropertyGrid and have overridden the standard editbox with a custom NumerictextBox by defining a custom attribute template as seen here:
<attributeTemplates:AccountSetupTemplates.NumericTextBox>
<DataTemplate DataType="{x:Type viewModels:AccountSetupViewModel}">
<Grid>
<TextBox DataContext="{StaticResource AccountSetupViewModel}" MaxLength="7" Text ="{Binding Attributes.Port}">
<i:Interaction.Behaviors>
<behaviors:NumericTextBoxBehavior/>
</i:Interaction.Behaviors>
</TextBox>
</Grid>
</DataTemplate>
</attributeTemplates:AccountSetupTemplates.NumericTextBox>
I then substitute the standard editbox from the PropertyGrid with this one. As you can see, this particular NumericTextBox is binded to the Port string.
The problem is that there are several other cases where a NumericTextBox should be used (about five more cases), where in each case, the control will need to be binded to a different string other than Port.
Currently, this means that I must define several such attributeTemplates, which seems silly. Is there a way that I can re-use the same attributeTemplate multiple times, with each instance binded to a different property string?
Thanks.
I have a WPF application. I have some labels and some datagrids which are bound to some public properties. Some of these properties are numerical values.
In the datagrids I have been using the line below to ensure the values only display two decimal places, which works. However when I use the same line below for my label it appears to have no effect on the display as the number shows to about 9 decimal places. I don't understand why it works for the datagrid but not the label?
StringFormat={}{0:0.##}
<Label Grid.Row="3" Grid.Column="1"
Content="{Binding Obs.Tstat, StringFormat={}{0:0.#}}"
HorizontalAlignment="Center" Foreground="{StaticResource brushLinFont}"
FontSize="13" FontWeight="Bold"/>
Updated code
<Label Grid.Row="3" Grid.Column="1"
Content="{Binding Obs.Tstat}" ContentStringFormat="{}{0:0.#}}"
HorizontalAlignment="Center" Foreground="{StaticResource brushLinFont}"
FontSize="13" FontWeight="Bold"/>
For label you need to use ContentStringFormat:
<Label Content="{Binding Obs.Tstat}" ContentStringFormat="{}{0:0.##}"/>
Reason:
Label's Content property is of type object and StringFormat is used only when binding property is of type String.
If you try your code with TextBlock's Text property it will work fine with StringFormat because Text property is of type string.
Just a quick addition I'd like to post along these lines in case anyone else runs in to it... My application uses localization since it has multi-country support, but we also support user's system settings. We noticed ContentStringFormat defaults to your UI culture.
That caused an issue in one of our forms where the user's machine-specific decimal places they had configured in windows settings were not respected when you specified the ContentStringFormat.
Since the ContentPresenter simply takes the string format without the converter culture you could normally specify in the binding , this means that a format of say: 0:N will only return two decimal places if English is my current UI culture, even though I have 5 decimals specified on my windows settings.
In our case we applied some binding overrides to work around this, but mainly just wanted to add this extra bit of info in case anyone else runs in to it ;)
I would like to assign datacontext and static values in a single Combobox. I tried like this,
this.courseList.DataContext = ldc.Courses.OrderBy(x => x.CourseID);
this.courseList.Items.Add("All");
this.courseList.Items.Add("Others");
But, it shows InvalidOperationException was Unhandeled that Items collection must be empty before using ItemsSource.
Then I tried like this,
this.courseList.Items.Add("All");
foreach (var items in ldc.Courses.OrderBy(x => x.CourseID))
{
this.courseList.Items.Add(items);
}
this.courseList.Items.Add("Others");
It works like this Image
Since I used an ItemTemplate in XAML design for this Combobox so it doesn't shows exactly in my way for this two value All and Others.
Here is the ItemTemplate
<DataTemplate x:Key="CourseTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CourseID}"/>
<TextBlock Text=" : "/>
<TextBlock Text="{Binding Path=CourseName}"/>
</StackPanel>
</DataTemplate>
...
<ComboBox Name="courseList" VerticalAlignment="Top" SelectionChanged="courseList_SelectionChanged" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" ItemTemplate="{StaticResource CourseTemplate}"/>
I want it to view according to DataTemplate for database entries and simple strings All and Others for All and Others respectively like the image below.
Any suggestions. Thank you.
As the error says, you can only use ItemsSource or Items directly, not a mix of the two. Since you essentially have two different types of data that you want to show differently you can add them all to a single bound collection and then let the templates do the work of rendering them differently. Step 1 is to build the single collection:
this.courseList.ItemsSource = new object[] { "All" }.Concat(ldc.Courses.OrderBy(x => x.CourseID)).Concat(new[] { "Others" });
You now have 2 types of data in the collection: String and your object which I'll assume is Course. The strings will show up as you want without any extra work but you need to apply the DataTemplate only to the Course objects. The simplest way is just to replace your x:Key with a DataType:
<DataTemplate DataType="{x:Type data:Course}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CourseID}"/>
<TextBlock Text=" : "/>
<TextBlock Text="{Binding Path=CourseName}"/>
</StackPanel>
</DataTemplate>
where the data xmlns corresponds to your Course object's namespace.
Next just remove the ItemTemplate attribute from your ComboBox and you should get what you're looking for.
If you have other places in the same scope where you're also binding Course objects but want them to display differently you can either scope the DataType template more locally (i.e. a Grid.Resources) or use a DataTemplateSelector on your ComboBox.
You can solve this by using ItemTemplate combined with converter.
Template:
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource ComboBoxItemConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
Converter:
public class ComboBoxItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Courses)
{
var course = value as Courses;
return course.CourseID + " : " + course.CourseName;
}
else
{
return value.ToString();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Since ldc.Courses is set as the DataContext, and you have ItemsSource="{Binding}" this means that the combobox is bound directly to your collection. This creates a problem when you try to add strings as you did since now the collection has both strings and Course objects.
I think this will probably be the easiest solution to understand. Starting with your second approach:
this.courseList.Items.Add("All");
foreach (var items in ldc.Courses.OrderBy(x => x.CourseID))
{
this.courseList.Items.Add(items.CourseID.ToString() + " : " + items.CourseName);
}
this.courseList.Items.Add("Others");
This will keep your combobox bound to a simple list of strings instead of objects, so as long as you don't need other information in the background this will be simpler to digest, if not try one of the other answers that mixes strings and objects into the same collection. This would also mean you don't need to set the DataContext, the ItemsSource, or the DataTemplate, so erase all that.
If you want to keep the template to add a little more control, again you may want to look at some of the other answers. Another alternative would be to create a parent class over your courses object that could also take the more general ALL and OTHER cases and create actual objects around them probably the most flexibility. This way you wouldn't end with a collection of both strings and objects, which will cause you to have to create special code cases anytime you manipulate the collection.
My UI is simple. In a Silverlight 5.0 application, I'm using MVVM and I let the user adds many textboxes as he wants to add in a ObservableCollection<Model> and a Button.
Model just have one property and its datatype is an integer.
The data template for this model is just a simply textbox.
<TextBox Text="{Binding Number}" />
So the idea is, when all the textboxes does not have any error, the command is enabled, but if any model has an error, the command should be disabled.
How can I implement this validation?
Thanks in advance.
You can simply throw an exception in appropriate property`s setter:
public int Number
{
get {//...}
set {
if(value >= 10)
throw new Exception("Number should be less than 10");
_number = number;
}
}
And your binding should be:
<TextBox Text="{Binding Number, Mode="TwoWay" ValidateOnExceptions="True"}" />
FrameworkElement has BindingValidationErrorEvent, which can be used for implement enable/disable command logic. Remember to set NotifyOnValidationError to True for your binding.
p.s.Also, i suggest you read about INotifyDataErrorInfo