WPF ComboBox MVVM strange behavior - wpf

I'm having a strange behavior when I associate and combobox to my viewmodel. The behavior is the following, when I change the selected value of the combo I do a validation of the new value and if this new value is invalid I keep the old value and discard the new one, in this way I don't raise the Inotifypropertychanged, but the getter from the property associated to the combobox is call anyway, this gets the old value that I want to show, but instead the combobox shows the new value, even though the selectedvalue of the combo as the old and correct value, I checked in debug mode. I don't know how can I solve this because I never saw this kind of behavior, any suggestions would be much appreciated.
This is the code of the XAML
<ComboBox Height="23" Name="cbxStatus" HorizontalAlignment="Left"
ItemsSource="{Binding Path=Status, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=SelectedStatus, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Value" Width="130" VerticalAlignment="Center"
IsEnabled="{Binding Path=StatusEnable, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
this is the viewmodel code, the property
public Config SelectedStatus
{
get
{
if (ApplicationAction == ApplicationAction.Add)
{
base.Object.State = configManager.BudgetInitStatus();
StatusEnable = false;
}
else
{
StatusEnable = true;
}
return base.Object.State;
}
set
{
if (base.Service.CanChangeBudgetStatus(base.Object, value))
{
base.Object.State = value;
base.Object.IsDirty = true;
}
RaiseOnPropertyChanged("SelectedStatus");
RaiseOnPropertyChanged("AssociateOrderButtonVisibility");
}
}
Thanks for the help

As indicated in my comment to Jay, the problem here is that WPF is setting the value and not listening to your change notification (which it is, after all, expecting). What you need to do is raise the property change notification outside the context of the current message. You could do this using the dispatcher, for example:
set
{
if (!valid)
{
// value is unchanged
Dispatcher.BeginInvoke(delegate { this.OnPropertyChanged(...) });
return;
}
// value is changed here
}
This will ensure the current data binding message is executed, then a separate message tells WPF that, "actually, the value you just provided to my setter is no longer the current value".
You could also use SynchronizationContext if you prefer. Either way, I admit it's a little hacky. Unfortunately, I don't know of a nice way around this. The fact is, WPF assumes that the value it passes to your setter is the effective value of the property. No amount of property change notifications within the context of the binding operation will convince it otherwise.

Setting your binding to
IsAsync=True
will work.

When the user changes the value in the combobox, it gets changed in the combobox, irrespective of your viewmodel.
If you change that value back and do not raise a property change notification, your viewmodel and view will be out of sync.
In short, when you reject the selected value you still need the property change notification.

Related

SelectedItem is updating when ListCollectionView changes

I am using WPF, MVVM, and entity framework.
I am working with a data entry application, and I am trying to enable cancel changes to work in my app. Where when changes are cancelled, all values reset to their original value. I think I have everything on the EF side setup correctly. Basically I just set all entities to unchanged if they are in the modified list.
My problem is when I come back to the ViewModel, and I am trying to re-setup all of the fields and derived properties. The biggest annoyance has been the collections. We have multiple combo box controls that we bind to a ListCollectionView and then I have an additional property in the view model that represents the SelectedItem. When I am resetting up the collections I was just allowing the process to re-initiate all the properties including the collections. When I change the collection, it tries to also change the selected property. The problem with this is if it changes the selected property the backing entity gets updated with the new values (as if the user selected an item), and I technically can't get the value back.
I was actually having a reverse problem when I was saving. After the save the form would go into its not edit mode and the value would be set to the old value. Re-opening the form in edit would load the correct value. To fix this I added to the form IsSynchronizedWithCurrentItem=true. But now I am having the problem reverse problem where the value goes back to old value during edit.
// View Code
<ComboBox Grid.Row="1"
Grid.Column="2"
ItemTemplate="{StaticResource TransformerTypeDisplayDataTemplate}"
ItemsSource="{Binding Path=TransformerTypeCollection}"
SelectedItem="{Binding Path=SelectedTransformerType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource AssetViewStateAwareComboBox}" Margin="0,0,0,2" VerticalAlignment="Bottom" />
//ViewModel Properties
private ListCollectionView<TransformerType> _transformerTypeCollection;
public ListCollectionView<TransformerType> TransformerTypeCollection
{
get { return _transformerTypeCollection; }
set { _transformerTypeCollection = value; RaisePropertyChanged("TransformerTypeCollection"); }
}
private TransformerType _selectedTransformerType;
public TransformerType SelectedTransformerType
{
get
{
return _selectedTransformerType;
}
set
{
_selectedTransformerType = value;
if (IsInEditMode)
{
BackingEntity.TransformerTypeID = _selectedTransformerType.ID;
BackingEntity.TransformerType = _selectedTransformerType;
}
RaisePropertyChanged("SelectedTransformerType");
}
}
// Setting the collection will trigger the set method for SelectedTransformerType
TransformerTypeCollection = TaskCoordinator.TransformerTypes.GetView();
My current work around for this problem is I keep around a state variable that says the collections have already been populated. And it skips the resetting the collections on the re-setup of the view model.

WPF DataGrid - how to stop user proceeding if there are invalid cells? (MVVM)

I'm implementing edit functionality in my DataGrid. The CellEditingTemplate of a typical cell looks something like this:-
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Grid.Column="0"
Text="{Binding Concentration, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=LostFocus}"
Validation.ErrorTemplate="{StaticResource errorTemplate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
In this example there is a TextBox bound to a property called "Concentration", which is a property of type double, with validation to ensure that it falls within a given range. The models that are being bound to the grid implement IDataErrorInfo by the way.
The problem is, although the error template highlights the cell when an invalid value is entered, there is nothing to stop me from moving focus away from that cell (or row). What's the preferred approach for handling validation with data grid editing? I wondered if I could prevent the user from leaving the cell until a valid value was entered, but that could be dangerous - they wouldn't be able to do anything (even close the app) until a valid value had been entered.
Another option might be to prevent the user from proceeding if the datagrid contains any invalid rows. But what's the best way to test this? IDataErrorInfo doesn't provide an "IsValid" property that I can examine on each of the rows' models.
Lastly I could just revert the model's property back to its previous "good" value (or a default value) if the user enters something invalid.
Any suggestions? I'm using MVVM by the way.
I use this to see if IDataerrorInfo has any errors for the object, a small snippet of the implementation:
protected Dictionary<string, string> _propertyErrors = new Dictionary<string, string>();
public bool HasErrors {
get { return (_propertyErrors.Count) > 0; }
}
Then I can handle the logic for what to do after evaluating this property. Do you want to prevent navigation, closing app, etc. Then you need to evaluate for errors from that code and then cancel that action.
I've used this method in the past to determine if a datagrid has errors:
private bool HasError(DataGrid dg,)
{
bool errors = (from c in
(from object i in dg.ItemsSource
select dg.ItemContainerGenerator.ContainerFromItem(i))
where c != null
select Validation.GetHasError(c)
).FirstOrDefault(x => x);
return errors;
}
Then it's only a matter of preventing the next action if the method returns true.

Invalid data in TextBox -- How to disable my command buttons?

Basically I have the following situation:
<TextBox Text="{Binding MyIntValue}" />
<Button prism:Click.Command={Binding MyCommand}" />
public Boolean CanDoCommand()
{
return (MyIntValue < 100);
}
public void DoCommand() { ... }
So here's the problem, if I type in the value of 25 the MyCommand becomes enabled. Afterwards, if I change it to 25A the Button is still enabled because the binding was not updated to reflect an error in my ViewModel. Instead, I only have an binding error on my View. This leaves the MyCommand button enabled and the MyIntValue still at 25.
How can I disable the button based on having any binding issues even if my ViewModel is proper?
Edit (What the poster is truly asking for):
How can I disable a button regardless
of what the CanExecute method returns
from the ViewModel based upon the View
having a BindingError?
<Button prism:Click.Command={Binding MyCommand,
UpdateSourceTrigger=PropertyChanged}" />
You must raise the command's can execute changed event when MyIntValue changes.
if your MyIntValue property is type of int your binding will never update when your input is 25A.
ony way to solve this is to use type of string and IDataErrorInfo on VM side.
another way is to use typeof Nullable int and a converter and set the value to null when its not what you expect.
EDIT:
How can I disable the button based on having any binding issues even if my ViewModel is proper?
your problem is that your VM and your UI is not in sync. if you type 25A your Vm seems right because it still has the 25, but your View has an BindingError. so your question should be how can i sync my view and viewmodel. (see my two suggestions)
EDIT:
another solution would be to prevent wrong input. so a Masked or RegexTextbox behavior should also work.

Why won't my WPF bound Visibility property update?

I have a textblock in my XAML where the Visibility is bound to a property in my viewmodel. When the window first loads, the value from the viewmodel determines the visibility correctly (I tried manually overriding the backing store variable value and it works great, hiding the control as I need). However, when I change the property value the visibility doesn't change.
Here's the XAML for the control:
<TextBlock Text="Click the button" Style="{StaticResource Message}" Visibility="{Binding NoResultsMessageVisibility}" />
The "NoResultsMessageVisibility" property that I bind to is this:
public Visibility NoResultsMessageVisibility
{
get { return _noResultsMessageVisibility; }
set
{
_noResultsMessageVisibility = value;
NotifyPropertyChanged("NoResultsMessageVisibility");
}
}
NotifyPropertyChange raises a PropertyChanged event for the provided name using standard INotifyPropertyChanged.
Can anyone spot my mistake?
EDIT
In response to the comments / answer so far.
The program is super simple so there's no parallelism / multithreading used.
The DataContext is set only once when the window loads, using:
new MainWindow { DataContext = new MainWindowViewModel() }.ShowDialog();
The binding does seem to work when first loaded. I've noticed as well that a textbox I have bound to a property isn't updating when I change the property. However, the property is definitely updating when I change the textbox as the value is used as the basis for a command that's bound to a button. As the text changes, the button is enabled and disabled correctly and when I click it the value from the property is correct. Again, if I set a value against the backing store variable, this shows in the textbox when the window first loads.
Don't see anything wrong with this, is it possible that the DataContext gets changed, so the binding breaks? (You only specify the path, so it's relative to the current DataContext)
Solved it. I'm a dozy dork :)
I have copied some code from another class and for some reason I'd added the PropertyChanged event to my viewmodel's interface, rather than implementing INotifyPropertyChanged on the interface. D'Oh!

WPF CheckBox's IsChecked property doesn't match binding source's value

In my WPF application I have a CheckBox whose IsChecked value is bound to a property in my viewmodel. Notice that I have commented out the actual line which sets the value in my viewmodel. It's the standard pattern:
View.xaml
<CheckBox IsChecked="{Binding Path=SomeProperty}" />
ViewModel.cs
public bool SomeProperty
{
get { return this.mSomeProperty; }
set
{
if (value != this.mSomeProperty)
{
//this.mSomeProperty = value;
NotifyPropertyChanged(new PropertyChangedEventArgs("SomeProperty"));
}
}
}
When I click the CheckBox I expect nothing to happen, since the value of this.mSomeProperty does not get set. However the observed behavior is that the CheckBox is being checked and unchecked regardless of the value of this.mSomeProperty.
What is going on? Why isn't my binding forcing the CheckBox to show what the underlying data model is set to?
Because WPF does not automatically reload from the binding source after updating the source. This is probably partly for performance reasons, but mostly to handle binding failures. For example, consider a TextBox bound to an integer property. Suppose the user types 123A. WPF wants to continue showing what the user typed so that they can correct it, rather than suddenly resetting the TextBox contents to the old value of the property.
So when you click the CheckBox, WPF assumes that it should continue to display the control state, not to re-check the bound property.
The only way I've found around this, which is not very elegant, is to raise PropertyChanged after WPF has returned from calling the property setter. This can be done using Dispatcher.BeginInvoke:
set
{
// ...actual real setter logic...
Action notify = () => NotifyPropertyChanged(...);
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, notify);
}
This could be made a bit less horrible by consolidating it into the NotifyPropertyChanged implementation so that you wouldn't have to pollute individual properties with this implementation concern. You might also be able to use NotifyOnSourceUpdated and the SourceUpdated attached event, but I haven't explored this possibility.

Resources