IDataErrorInfo error message triggers, but no message is shown - c#

Hello fellow programmers. I have this strange issue on Silverlight MVVM pattern using IDataErrorInfo. It's my first question made here in StackOverflow, so I hope I manage to explain it correctly. Here's the deal:
I Have this huge Job Register ChildWindow that has some controls that should only be enabled if a checkbox called HasPrevision is checked. The idea is: if the job has a Prevision, then all the controls related to the prevision are Enabled, otherwise, they are Disabled.
Now, if there is a prevision, the user MUST choose a Prevision Type in the PrevisionType ComboBox. So, if the HasPrevision checkbox is CHECKED, the PrevisionType must be selected, but if the HasPrevision checkbox is NOT CHECKED, the PrevisionType comboBox can be left unselected.
Here are the XAML for those controls (I won't show all the xaml here because it's rather big, but I believe that the problem can be solved just by showing those lines):
<!-- The HasPrevision CheckBox -->
<CheckBox Grid.Column="1"
Grid.Row="3"
x:Name="cbxHasPrevision"
IsChecked="{Binding SelectedJob.HasPrevision, Mode=TwoWay}"
Margin="0,20,2,0" />
<!-- The PrevisionType ComboBox -->
<telerik:RadComboBox Grid.Column="1"
Grid.Row="4"
x:Name="cmbPrevisionType"
IsEnabled="{Binding HasPrevision, Converter={StaticResource ControlesCadastroProcessoConverter}, ConverterParameter=IsEnabled}"
ItemsSource="{Binding PrevisionTypeList, Mode=OneWay}"
SelectedItem="{Binding SelectedJob.PrevisionType , Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, ValidatesOnNotifyDataErrors=True}"
Margin="0,4,48,4"
Height="22"
DisplayMemberPath="DsPrevisionType"/>
So, as you can see, the HasPrevision and the PrevisionType properties are binded to a property in my ViewModel called SelectedJob. SelectedJob is a Job object. All I want to do is use the IDataErrorInfo interface in my Job Class to identify if the PrevisionType is selected. Now, I can only require a PrevisionType if the HasPrevision property is "true", so my job class has this code for the IDataErrorInfo:
public string this[string columnName]
{
get
{
switch (columnName)
{
case "PrevisionType":
if (HasPrevision && (PrevisionType== null))
return "Select a PrevisionType";
break;
}
return null;
}
}
This sounds very logical to me. When I debug the code, it actually works: the switch identifies the columnName, it enters the case "PrevisionType" and it actually runs the return code inside the If statement returning the error message, not the default "return null" clause. But the error message don't show the error! Even worse: the combo box wont event validate automatically.
When the "Save" button is clicked, I check for errors and set the PrevisionType comboBox to the InvalidFocused state. The comboBox DOES get the red border around it indicating that it is in an error state, but the error message is blank. When I hover my mouse of the control, an empty small red box appears, with nothing on it.
Even more weird than that: if I REMOVE the "HasPrevision" check from the IF statement, leaving the code as follow, the message is shown and the control is validated automatically!!!
public string this[string columnName]
{
get
{
switch (columnName)
{
case "PrevisionType":
if (PrevisionType== null)
return "Select a PrevisionType";
break;
}
return null;
}
}
Everything I can think of is that since I'm trying to validate a property based on another property value, somewhere in the middle of the set's and get's the Silverlight environment gets "confused"? I don't know, I've been trying to find a solution for this for 2 days with no success.
Here are some of the other codes that I believe that are relevant.
The properties in my Job Class:
private bool _HasPrevision;
[DataMember]
public bool HasPrevision
{
get { return _HasPrevision; }
set
{
_HasPrevision= value;
RaisePropertyChanged("HasPrevision");
}
}
private PrevisionType.PrevisionType _PrevisionType { get; set; }
[DataMember]
public PrevisionType.PrevisionType PrevisionType
{
get { return _PrevisionType; }
set
{
_PrevisionType = value;
RaisePropertyChanged("PrevisionType");
}
}
My SelectedJob property in my ViewModel is:
public const string SelectedJobPropertyName = "SelectedJob";
private Objects.Register.Job.Job _SelectedJob = null;
public Objects.Register.Job.Job SelectedJob
{
get
{
return _SelectedJob;
}
set
{
if (_SelectedJob == value)
return;
var oldValue = _SelectedJob;
_SelectedJob = value;
RaisePropertyChanged(SelectedJobPropertyName , oldValue, value, true);
}
}
And my code for the Save Button is:
[...]
if(SelectedJob["PrevisionType"] != null)
{
VisualStateManager.GoToState(cmbPrevisionTypeControl, "InvalidFocused", true);
return;
}
[...]
Thanks in advance and I'm sorry if I got a little bit confused on explaining this.
EDIT 1: I had already read on those questions here, but they don't quite help me:
IDataErrorInfo - not seeing any error message even though one gets picked up
Silverlight IDataErrorInfo message does not show in custom control's textbox

I'm using the Telerik RadComboBox in similar fashion and I have no issues getting the error message displayed. Here's my XAML for your reference, hope this helps...
<TextBlock Text="Select a User to Assign the Current Step To:" Grid.Row="3" Visibility="{Binding ShowAssigneeUser}"/>
<telerik:RadComboBox x:Name="cmbAllActiveCrmUsers"
IsEditable="True"
Grid.Row="4"
telerik:TextSearch.TextPath="FullName"
Text="{Binding FullName, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
VerticalAlignment="Center"
Visibility="{Binding ShowAssigneeUser}"
ItemsSource="{Binding AllActiveCrmUsersExceptCurrentUser}"
SelectedIndex="{Binding DefaultAssigneeUserIndex, Mode=TwoWay}"
ItemTemplate="{StaticResource ComboBoxMultilineTemplate}"
Style="{StaticResource ComboBoxStyle}"
Command="{Binding AssignUserChangeCommand}"
CommandParameter="{Binding SelectedIndex, ElementName=cmbAllActiveCrmUsers}"
>
<telerik:RadComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</telerik:RadComboBox.ItemsPanel>
</telerik:RadComboBox>

Related

C# WPF MVVM Textbox Clearing without breaking Command Bindings?

googling for this showed me that this is often a problem but never reallay solved.
I do have an App/Prgramm in C#, i'll try to be mvvm conform.
I do have an window, in it a UserControl show different views.
One of my view contains a textbox, the text of the textbox is bound to a proptery of the VM.
My textbox got 2 inputbindings, for "enter" and "return" - both leading to same command.
On hitting "enter" the value of the textbox should be processed, the textbox shoud be cleared and refocused ... This works .... One Time ....
Clearing the textbox with String.Empty breaks the Bindings ... this could be found in several postings here ... the most Solution is textboxname.clear() ...
But i dont have the "textboxname" in the viewmodel, only in code-behind, but all my logic is in the VM ... So can somebody pls help me sort things out, how i could clear the textbox without breaking the bindings to input text and hit enter more then one time ?
My Code
<TextBox x:Name="tb_checkinbox" Grid.Row="0" Grid.Column="1" Width="200" Height="25" VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding CheckInNumber}">
<TextBox.InputBindings>
<KeyBinding Command="{Binding OnCheckInEnterCommand}" Key="Return"/>
<KeyBinding Command="{Binding OnCheckInEnterCommand}" Key="Enter"/>
</TextBox.InputBindings>
</TextBox>
public CheckinVM()
{
OnCheckInEnterCommand = new RelayCommand(OnCheckInEnter, CanCheckInEnter);
}
private string _checkInNumber;
public string CheckInNumber
{
get { return _checkInNumber; }
set { SetProperty(ref _checkInNumber, value); }
}
public ICommand OnCheckInEnterCommand { get; set; }
public void OnCheckInEnter(object value)
{
CheckInNumber = String.Empty;
/// More to do
}
public bool CanCheckInEnter(object value)
{
return true;
}
The assignment
CheckInNumber = string.Empty;
does not "clear" any Binding. Your conclusion is wrong.
You do however only get empty strings - after clearing - in the setter of the CheckInNumber property. In order to get property updates not only when the TextBox loses focus or you reset the source property, set UpdateSourceTrigger=PropertyChanged on the Text Binding:
<TextBox ... Text="{Binding CheckInNumber, UpdateSourceTrigger=PropertyChanged}">

Why is the DataGrid replacing a bound property with an old, invalid value when edited?

I've got a simple DataGrid bound to an ObservableCollection of view models. One column is a DataGridTemplateColumn whose CellTemplate is a TextBlock and CellEditingTemplate is a TextBox (I realize I could use a DataGridTextColumn, but I want to be explicit about which controls to use). The TextBox is configured to validate on errors.
<DataGrid
ItemsSource="{Binding People}"
AutoGenerateColumns="False"
>
<DataGrid.Columns>
<DataGridTemplateColumn Header="First Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The view model for each item (PersonViewModel) simply defines a FirstName property, and implements IDataErrorInfo for validation. I'm using the MVVM Light Toolkit for property notification.
public class PersonViewModel : ViewModelBase, IDataErrorInfo
{
private string firstName;
public string FirstName
{
get => firstName;
set => Set(nameof(FirstName), ref firstName, value);
}
public string this[string columnName]
{
get
{
if (columnName == nameof(FirstName))
{
if (string.IsNullOrEmpty(FirstName))
return "First name cannot be empty";
}
return null;
}
}
public string Error => null;
}
In addition, I've got a button that, when clicked, sets the FirstName of every person to "Hello".
public ICommand HelloCommand =>
new RelayCommand(() =>
{
foreach (var person in People)
person.FirstName = "Hello";
});
Here's the issue: when I enter an invalid FirstName (that is, I set it to the empty string), and then click on the Hello button, it correctly replaces the FirstName with "Hello". But then if I try to edit it again, the FirstName is immediately replaced with the empty string.
As a test, I made it so that it's an error to have the string "a". Doing the same steps as above, the "Hello" string is replaced with "a" in the TextBox. It's as if the TextBox doesn't know that FirstName was changed to "Hello," even though the TextBlock correctly displays it and they're both bound to the same property.
Does anyone know what's going on or ways in which to solve this issue? The behavior I expected was for the TextBox to contain the value the bound property changed to (regardless of whether there was a validation error on that TextBox).
Note: I'm using .NET 4.0 because I have to.
Obviously, the reason of this behavior is that at the time of HelloCommand executing, the TextBox that initiated validation error does not already exist. And hence clearing of validation error can't proceed usual way.
It is hard to say why the new value is not taken from the ViewModel at the moment of new TextBox creating and binding restoring. Even more interesting is where this erroneous value comes from. One may think, that if we take the new value of the FirstName property from the ViewModel and set it to TextBox.Text property in the DataContext_Changed event handler of the TextBox, then it should solve problem, because at this moment both the TextBox and the ViewModel are having a new (valid) value, and there are just no room where to take the wrong one from. But magically it still comes from somewhere :)
Happily, there is a trick that helps to get around the problem. The idea is to cancel all pending binding operations before executing HelloCommand. It is not too obvious why it should work. After all at this point the BindingExpression that caused the error is not exists. Nevertheless it works.
Give name to DataGrid:
<DataGrid x:Name="myGrid" ItemsSource="{Binding People}" AutoGenerateColumns="False">
Add click handler to the button:
<Button Content="Hello" Command="{Binding HelloCommand}" Click="Hello_Click"/>
with that code
private void Hello_Click(object sender, RoutedEventArgs e)
{
foreach (var bg in BindingOperations.GetSourceUpdatingBindingGroups(myGrid))
bg.CancelEdit();
}
UPD
As GetSourceUpdatingBindingGroups is not available in .NET 4.0, you could try this way:
private void Hello_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < myGrid.Items.Count; i++)
{
DataGridRow row = (DataGridRow)myGrid.ItemContainerGenerator.ContainerFromIndex(i);
if (row != null && Validation.GetHasError(row))
{
row.BindingGroup?.CancelEdit();
}
}
}
It is not so elegant, but does literally the same.

WPF - Hiding a ComboBox Selection

I have a combobox which allows users to select from a collection of images. Because of the size of the images I would like to not display the image in the combobox once an image has been selected. I simply want nothing displayed in the combobox when an item is selected.
So far I have tried setting the selectedImageIndex to -1 once the selectedImageSource has been set when the user makes a selection however this did not work as the first image at [0] is still displayed in the combobox by default. I am using MVVM.
XAML
<ComboBox Grid.Row="1" SelectedIndex="{Binding SelectedImageIndex}" ItemsSource="{Binding SymbolImageCollection}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Img}" Width="50" Height="50"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="300" HorizontalAlignment="Left"/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
View Model
public ObservableCollection<SymbolImage> SymbolImageCollection { get { return AppearanceLayerProperties.Instance.SymbolImageCollection; } }
private string _selectedImageSource;
public string SelectedImageSource
{
get { return _selectedImageSource; }
set
{
SetProperty(ref _selectedImageSource, value);
//SelectedImageIndex = -1;
}
}
private int _selectedImageIndex;
public int SelectedImageIndex
{
get { return _selectedImageIndex; }
set
{
var selectedImage = AppearanceLayerProperties.Instance.SymbolImageCollection[value].ImgSource;
SelectedImageSource = selectedImage;
SetProperty(ref _selectedImageIndex, -1);
}
}
Changing the selection back to null after selecting an item of the ComboBox is not good practice, because if you need to use the selected value, it is not available anymore.
A solution could be to have a different template for the selected item of the ComboBox. That way, you could remove the image, but in its place put something else, like text, so the user as an idea of what item is selected. Here is a previous StackOverflow post explaining how to do this :
Can I use a different Template for the selected item in a WPF ComboBox than for the items in the dropdown part?
I hope this helps!

ComboBox selected item binding not showing the initial value - then working OK

I have a weird problem with something simple I suppose.
I have a combobox with two bindings set up - one for ItemsSource and another for SelectedItem.
The selected item is not working on initial startup, but then it works OK. Output does not indicate any binding problems, I have also set up a TextBlock with the same binding to see if it works - and it does.
Here's the code
<ComboBox IsSynchronizedWithCurrentItem="True" IsEditable="False"
Name="ProgramsCollectionComboBox"
SelectedItem="{Binding ElementName=ThisUc,
Path=SelectedProgram}"
ItemsSource="{Binding ElementName=ThisUc,
Path=ProgramsCollection}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Text="{Binding ElementName=ThisUc,
Path=SelectedProgram.Name, Mode=TwoWay}" />
The property:
private Program _selectedProgram;
public Program SelectedProgram
{
get
{
if (_selectedProgram == null)
{
_selectedProgram = new Program(Settings.Default.SelectedProgramPath);
}
return _selectedProgram;
}
set
{
_selectedProgram = value;
Settings.Default.SelectedProgramPath = SelectedProgram.PathProgramFolder;
RaisePropertyChanged("SelectedProgram");
}
}
It saves and reads the settings OK, the initial values is shown in the textblock below the combobox, when I change the selected item, the textblock is updated, the settings are changed and everything works fine - except for the fact that on app startup, selected item is not selected.
Thanks for help!
There are two reasons your initial binding is not working. First, as Jehof has mentioned himself, is the fact that you're setting your SelectedProgram to an item that is not part of the ProgramsCollection.
Furthermore, when you are setting the initial value of your SelectedProgram you are doing so in the getter, where PropertyChanged is not invoked and thus the binding will never be aware of that change. You can either invoke PropertyChanged when initializing it in the getter:
...
get
{
if (_selectedProgram == null)
{
_selectedProgram = _programsCollection?.FirstOrDefault();
RaisePropertyChanged("SelectedProgram");
}
return _selectedProgram;
}
...
Or even better, set the default value on the private field:
private Program _selectedProgram = _programsCollection?.FirstOrDefault();
...
The getter of your property SelectedProgram should return a value of your ProgrammsCollection and not a new instance if it is null.
If the value is not part of the collection that is bound to the combobox it is not displayed.

Update datasource from control

I'm a bit new to the idea of bindings in C#. I currently have an application using MVVM that I'm working on. Say I have, for example, a list like so:
List<string> Items = new List<string>()
{
"Item1",
"Item2",
"Item3",
}
Bound to this list are three textboxes like so:
In the XAML:
<TextBox Name="TextBox1" text="{Binding TextSource1 Mode=TwoWay}">
<TextBox Name="TextBox2" text="{Binding TextSource2 Mode=TwoWay}">
<TextBox Name="TextBox3" text="{Binding TextSource3 Mpde=TwoWay}">
And in the code:
Public string TextSource1
{
get { return Items[0]; }
set { Items[0] = value; }
}
Public string TextSource2
{
get { return Items[1]; }
set { Items[1] = value; }
}
Public string TextSource3
{
get { return Items[2]; }
set { Items[2] = value; }
}
Say the displayed value in the first textbox is currently "Item1" as it is in the list. From here the user changes it to "Item4". How would I update the list from that? Does it update automatically if it's set to TwoWay?
I know in the code if I were to change a value in the list, say:
Items[2] = "Item4";
I can update the textbox by calling
RaisePropertyChanged("TextSource3");
But that's not of much help to me at the moment.
There are a couple of things to note. First when by default, a TextBox won't propagate the text changes until it loses focus. This can happen if you tab out of the control, or click on another control that can receive focus (like the other TextBoxes).
You can change this behavior by setting the UpdateSourceTrigger on the Binding (you also don't need Mode=TwoWay on a TextBox as it is the default mode):
<TextBox Name="TextBox1" text="{Binding TextSource1, UpdateSourceTrigger=PropertyChanged}" />
The second is that your properties need to raise the PropertyChanged event in their setter. While this isn't strictly necessary for a single Binding to work, you will want to do it in case you decide to have other controls bound to the same property.
Change type of Items to ObserableCollection<String>
Change binding of textbox to:
<TextBox Name="TextBox1" text="{Binding Items[0], Mode=TwoWay}">
<TextBox Name="TextBox2" text="{Binding Items[1], Mode=TwoWay}">
<TextBox Name="TextBox3" text="{Binding Items[2], Mpde=TwoWay}">
Because of ObservableCollection's internal notification mechanism, when you modify one of its element, it will notify UI.
Say the displayed value in the first textbox is currently "Item1" as it is in the list. From here the user changes it to "Item4". How would I update the list from that? Does it update automatically if it's set to TwoWay?
Yes it updates automatically when it loss focus.

Categories