C# WPF MVVM Textbox Clearing without breaking Command Bindings? - c#

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}">

Related

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.

Handling WPF Editable combobox when entered text is not a part of datsource

I have a Combobox in WPF, I have set Is Editable="true" which allows me enter any text in the combobox. I would like to restrict users from entering text outside datasource.
Xaml:
<ComboBox Name="service" Margin="0,0,0,4"
IsEditable="True"
Grid.Column="1"
Grid.ColumnSpan="2" Grid.Row="4"
SelectedValuePath="Id"
DisplayMemberPath="Service"
SelectedValue="{Binding Controller.Service1}"
ItemsSource="{Binding}" />
C#:
System.Data.DataView vw = tableAdapterServices.GetData().DefaultView;
service.ItemsSource = vw;
service.SelectedIndex = 0;
I do not want to allow users to enter text which is not present in the datasource, or handle it if the user enters any other text.
Update:
Thanks for the solution #Vishal, LostFocus event is handling the issue, but it gave rise to another issue. I have a button which is used to submit the combobox value along with other textbox values to the server. I am setting default value in the combobox in lostfocus event. But I need to prevent the button click event if some value other that datasource value is added in combobox.
You can check for selectedIndex in Lostfocus event :
private void ComboBox_LostFocus(object sender, EventArgs e)
{
if(((ComboBox)sender).SelectedIndex == -1)
{
//Text entered by user is not a part your ItemsSource's Item
SaveButton.IsEnabled = false;
}
else
{
//Text entered by user is a part your ItemsSource's Item
SaveButton.IsEnabled = true;
}
}
You can try handling the ComboBox's TextInput or PreviewTextInput events, doing the text search yourself, selecting the most appropriate item, and setting "e.Handled = true."
This works for a single character (i.e. if you enter the letter "j", it will select the first item that contains a "j" or "J"), but I'm sure there's a way to do this with your control. Just include a little more logic to achieve this.
private void MyComboBox_PreviewTextInput(object sender, TextCompositionEventArgs e) {
foreach (ComboBoxItem i in MyComboBox.Items) {
if (i.Content.ToString().ToUpper().Contains(e.Text.ToUpper())) {
MyComboBox.SelectedItem = i;
break;
}
}
e.Handled = true;
}
Ok, from what I understand the behaviour of the combobox should disregard an inserted character if there is no item in the datasource that contains the resulted string.
I believe you are doing it in a MVVM style since you are using binding. What you can do is to bind the ComboBox text to a property and the PreviewTextInput event to a command and do the filtering there.
XAML:
<ComboBox Name="service"
Margin="0,0,0,4"
IsEditable="True"
Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="4"
SelectedValuePath="Id"
DisplayMemberPath="Service"
SelectedValue="{Binding Controller.Service1}"
ItemsSource="{Binding}"
Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewTextInput">
<cmd:EventToCommand Command="{Binding TextInputCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Combobox>
C# ViewModel:
public RelayCommand<object> TextInputCommand { get; set; }
public bool CanExecuteTextInputCommand(object param)
{
return true;
}
public void ExecuteTextInputCommand(object param)
{
TextCompositionEventArgs e = param as TextCompositionEventArgs;
string currentText = this.Text;
string entireText = string.Format("{0}{1}", currentText, e.Text);
var item = this.Items.Where(d => d.StartsWith(entireText)).FirstOrDefault();
if (item == null)
{
e.Handled = true;
this.Text = currentText;
}
}
Where Items is the ObservableCollection containing the items (in this case it's a list of strings) and Text is the property binded to the Combobox text.
EDIT: Ok so what you need to do to make it work is to go to your project, right click on References, choose Manage NuGet Packages, search and install MVVM Light. Two dlls that start with GalaSoft will be added to your references. After this, in your xaml code add these namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
What this allows you to do is to bind an event to a ICommand object.

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.

IDataErrorInfo error message triggers, but no message is shown

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>

It doesnt change property,which is binded(mode TwoWay)

I have text box,i binded its Text property to MainTxtBoxTxt property in ModalView of Window.When MainTxtBoxTxt changes it affects to TextBox,but when Text property of TextBox changes it doesnt affect to MainTxtBoxTxt in ModelView.What is the problem?
<cstmTxtBox:CustomTextBox Grid.Row="0" TextWrapping="Wrap" FontSize="16" x:Name="TxtBox" cstmTxtBox:CustomTextBox.CaretIndex="{Binding Path=CaretIndex, Mode=TwoWay}" Text="{Binding Path=MainTxtBoxText,Mode=TwoWay}" >
CustomTxtBox
public class CustomTextBox : TextBox
{
public CustomTextBox()
{
}
public static DependencyProperty CaretIndexProperty =DependencyProperty.RegisterAttached( "CaretIndex",typeof(int),typeof(CustomTextBox),new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender));
public static void SetCaretIndex(UIElement element, int value)
{
element.SetValue(CaretIndexProperty, value);
}
public static int GetCaretIndex(UIElement element)
{
return (int)element.GetValue(CaretIndexProperty);
}
}
Try this
Text="{Binding Path=MainTxtBoxText,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Use following:
<cstmTxtBox:CustomTextBox Grid.Row="0" TextWrapping="Wrap" FontSize="16" x:Name="TxtBox" cstmTxtBox:CustomTextBox.CaretIndex="{Binding Path=CaretIndex, Mode=TwoWay}" Text="{Binding Path=MainTxtBoxText,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
By default text box update view model on focus lost. Specifying UpdateSourceTrigger=PropertyChanged will update view model every time text in text box changes.
You were missing UpdateSourceTrigger property in your code which specifies how the change to data should reflect in the model property. To elaborate, UpdateSoruceTrigger property has four options to select from :-
Default - which returns the default UpdateSourceTrigger value of the target dependency property. It varies with control.
LostFocus - Changes reflect when focus move away from the current control.
PropertyChanged - reflect changes as soon as data changes. For a textbox, whenever a key is pressed, the changes occur.
Explicit - As the name suggests, it occurs on your command. you must call the UpdateSource method or the changes will not propagate back to the source

Categories