WPF databinding: how is detected an item change? - wpf

I have some issue with WPF databinding, and I hope to be clear in my explaination because I fear that the problem is very subtle.
I have essentially a WPF UserControl with a bunch of ComboBox, each one is chained to each other. I mean that the first combobox is filled with some elements, and when the user select and item, the second combobox is filled with elements based on the previous selection, and so on with other combox.
All combobox are binded with UpdateSourceTrigger=LostFocus.
The code for a ItemsSource property of a combo looks like this:
private ICollectionView allTicketTypesView;
public IEnumerable<TicketTypeBase> AllTicketTypes
{
get { return this.allTicketTypesView.SourceCollection.Cast<TicketTypeBase>(); }
private set
{
IEnumerable<TicketTypeBase> enumerable = value ?? new TicketTypeBase[0];
ObservableCollection<TicketTypeBase> source = new ObservableCollection<TicketTypeBase>(enumerable);
this.allTicketTypesView = CollectionViewSource.GetDefaultView(source);
this.OnPropertyChanged("AllTicketTypes");
}
}
The code for a SelectedItem property of a combo is similar to this code:
private TicketTypeBase ticketType;
public TicketTypeBase TicketType
{
get { return this.ticketType; }
set
{
this.ticketType = value;
this.OnPropertyChanged("TicketType");
this.UpdateConcessions();
}
}
I'm experiencing a subtle problem:
when i move with keyboard and/or mouse over my combo, I see that often propertychanged is called also when I actually don't change any of the items of the list.
I mean that a combo is filled with elements, and an item is selected: moving over the combo with the keyboard trigger the propertychanged (and let the other combo to be updated, that is an indesidered behavior), but the element itself is the same.
I see this behavior in a combobox that is binded with a list of strings (so I suppose no error on Equals/GetHashCode implementation) and this behavior happens everytime except the first time.
I've fixed the code with this:
private string category;
public string Category
{
get { return this.category; }
set
{
bool actuallyChanged = !String.Equals(this.category, value);
this.category = value;
this.OnPropertyChanged("Category");
if (!actuallyChanged)
{
string format = String.Format("Category '{0}' isn't changed actually", value);
Trace.WriteLine(format);
}
else this.UpdateTicketTypes():
}
}
But of course I don't like this code that add logic to the setters.
Any suggestion about how to avoid this behavior?
I hope to be clear, and I'm ready to explain better my problem if someone don't understand clearly.

It is not unreasonable for your model to check whether a value used in a property setter is actually different from the current value. However a more 'standard' implementation would look like the following:
private string category;
public string Category
{
get { return this.category; }
set
{
// check this is a new value
if(Object.Equals(this.category, value))
return;
// set the value
this.category = value;
// raise change notification
this.OnPropertyChanged("Category");
// compute related changes
this.UpdateTicketTypes():
}
}

Just a guess but can you implement SelectedValue binding instead of SelectedItem? SelectedValue (for value types like int, string, bool etc.) do no refresh upon keyboard or mouse focuses and even when ItemsSource (with CollectionView) changes coz the change notifications in the source (or model) not fire as value types do not change by reference.

Related

ListBox databinding and immutability

I'm having some problems with ListBox databinding and immutability. I have a model that provides a List of some elements and a ViewModel that takes these elements and puts them to an ObservableCollection which is bound to the ListBox.
The elements, however, are not mutable so when they change - which happens when user changes ListBox's selection or in a few other scenarios - the model fires up an event and the ViewModel retrieves a new List of new elements instances and repopulates the ObservableCollection.
This approach works quite well - despite being obviously not optimal - when user interacts with the ListBox via mouse (clicking) but fails horribly when using keyboard (tab to focus current element and then using mouse arrows or further tabbing). For some reason the ActiveSchema gets always reset to the first element of the Schemas[*].
The ActiveSchema setter gets called for the schema user switched to, then for null, and finally for the first value again. For some reason the two last events don't happen when invoked via mouse.
PS: Full code can be found here
PPS: I know I should probably rework the model so it exposes ObservableCollection that mutates but there're reasons why trashing everything and creating it from scratch is just a bit more reliable.
//ListBox's Items source is bound to:
public ObservableCollection<IPowerSchema> Schemas { get; private set; }
//ListBox's Selected item is bound to:
public IPowerSchema ActiveSchema
{
get { return Schemas.FirstOrDefault(sch => sch.IsActive); }
set { if (value != null) { pwrManager.SetPowerSchema(value); } }
}
//When model changes:
private void Model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(IPowerManager.PowerSchemas))
{
updateCurrentSchemas();
}
}
private void updateCurrentSchemas()
{
Schemas.Clear();
var currSchemas = pwrManager.PowerSchemas;
currSchemas.ForEach(sch => Schemas.Add(sch));
RaisePropertyChangedEvent(nameof(ActiveSchema));
}

Setting observable object to NULL == CRASH

I have a List bound to a (Telerik) GridView. The selected item is a separate variable of type T which is assigned the object of the selected row in the GridView when the user clicks on a row. T is derived from ObservableObject. This means I am using MVVM Light Toolkit.
I need to deselect the row from my ViewModel in certain situations. On the GridView control this works, if the selected item is set to NULL in the ViewModel. Whenever I do this, MVVM reports a crash (NPE). I debugged it and saw that it is failing in ObservableObject.cs. It calls a method
protected bool Set<T>(
Expression<Func<T>> propertyExpression,
ref T field,
T newValue)
and crashes one line before return when calling RaisePropertyChanged(propertyExpression)
I don't know if this is working as designed or not. My problem is, that I need to set the selected Object to NULL in the ViewModel to deselect a row of my GridView in the View. I CANNOT use CodeBehind for the deselection!
Code I have:
public ObservableCollection<ContractTypeDto> ContractTypes { get; private set; }
public ContractTypeDto SelectedContractType
{
get { return _selectedContractType; }
set
{
Set(() => SelectedContractType, ref _selectedContractType, value);
RaisePropertyChanged(() => SelectedContractType);
}
}
When you click on a row in the grid it opens a new UserControl containing lots of details of this record. This control has its own ViewModel. I store the calling view Model (where the selected item is stored). When the page (control) is closed (destroyed) I have to deselect the row in the grid. I call a method like so:
protected void DeselectCallersSelectedItem()
{
if (CallingObject == typeof(ContractTypeListViewModel))
{
var vm = SimpleIoc.Default.GetInstance<ContractTypeListViewModel>();
vm.SelectedContractType = null;
}
}
Any ideas?
To remove the collection you can either set the SelectedItem property to null or clear the SelectedItems.
gridViewName.SelectedItem = null;
gridViewName.SelectedItems.Clear();
Without showing the code, we cannot precisely help you. A solution I think you can do is to implement the INotifyPropertyChanged interface in your view model and bind the selected item to a property of that type. Also check the output window if there is any binding failure.

Extended WPF Toolkit - CheckComboBox

Is anyone aware of a way to manually enable (turning on the tick) on the Check Boxes within the CheckComboBox for WPFToolkit?
Unfortunately, the Items in the Combo-box are all strings.
I'm trying to enable all flags when "Select All" checkbox is ticked.
This is a rather late response but I thought it best to post this in case it helps someone out. I have used the following approach for the WPFToolkit version:
public class Descriptor : INotifyPropertyChanged
{
private bool isSelected;
public bool IsSelected
{
get
{
return this.isSelected;
}
set
{
if (this.isSelected != value)
{
this.isSelected = value;
// Raise INotifyPropertyChanged
}
}
}
public string Name { get; set; }
}
Create a collection of these and then assign them to the ItemsSource of the CheckComboBox.
To handle select all we have an option labelled: "" as the first item in the collection, then if this item is ticked all the items are de-selected and the all case is handle under the hood. To handle the selection Changed it does involve adding an event to the Descriptor class and firing it each time the IsSelected property is changed.
I eventually tossed out Extended WPFToolkit due to it's inability to access the checkboxes directly.
Instead I created a ComboBox and manually defined Checkboxes within it, which I access directly by name, and there able to implement a "Select All" by using it's [Checked/Unchecked[ event, and use the ComboBox SelectionChanged to show a default value that expresses what has been selected in a CSV format.
Maybe be clunky, but it gets the job done.
PS. I did not need to even bother with a DataTemplate for the ComboBox
One way in the code Behind is
var ComboSelector = MyCheckComboBox as Xceed.Wpf.Toolkit.Primitives.Selector;
foreach(var item in MyCheckComboBox.Items)
ComboSelector.SelectedItems.Add(item);

WPF datagrid validation inconsistency

I've got a DataGrid that I'm binding to an ObservableCollection of "Customer" classes, implementing IDataErrorInfo. One of the properties on the Customer class is an int, and in my IDataErrorInfo implementation I check that it's within a valid range, e.g.:-
public class Customer : IDataErrorInfo
{
public int PercentDiscount { get; set; }
... other properties & methods removed for clarity
public string this[columnName]
{
get
{
if (PercentDiscount < 0 || PercentDiscount > 10)
return "Percent Discount is invalid";
}
}
}
In my XAML code-behind I handle a couple of events. In the PreparingCellForEdit event I store a reference to the row being edited:-
private void DataGrid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
_rowBeingEdited = e.Row;
}
Then in the RowEditEnding event, I take some action if the row is in an invalid state (in my case I revert the Customer properties back to their previous "good" values):-
private void DataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
if (_rowBeingEdited != null)
{
var errors = Validation.GetErrors(_rowBeingEdited);
if (errors.Count > 0)
{
.. do something
}
}
}
This works fine if the user enters a numeric value that fails my validation rule, but if the user enters a non-numeric value then the RowEditEnding event never fires and the cell remains in an edit state. I assume it's because WPF fails to bind the non-numeric value to the int property. Is there any way I can detect/handle when this happens?
Last resort is to change the PercentDiscount property to a string, but I'm trying to avoid going down this route.
Edit - I've just found that I can successfully handle both types of error using the CellEditEnding event instead of RowEditEnding. A new problem has appeared though - if I enter an invalid value into the cell then press Enter, the underlying property doesn't get updated, so when CellEditEnding fires Validation.GetErrors is empty. The end result is that the row leaves edit mode but still shows the invalid value in the cell with red border. Any idea what's going on now?
This may not be much of an answer especially since you already mentioned it, but I've fought with DataGrid validation for a while and ended up resorting to making my backing values be strings. You'll notice in the output window of the debugger that a binding or conversion exception happens when you type an alpha character into a DataGridColumn bound to an int.
You can get different behavior by changing the UpdateSourceTrigger, or by putting a converter in between the binding and the property, but I never got exactly what I needed until I backed the values with strings.
I suppose you could also try creating your own DataGridNumericColumn derived from DataGridTextColumn and maybe you'd have more control over the binding/validation behavior.
I struggled to find a good solution for this, but I saw some other people messing with the CellEditEnding event and I ended up, coming up with this code to revert values if they fail conversion:
private void CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
if (e.EditingElement is TextBox)
{
var cellTextBox = (TextBox)e.EditingElement;
var cellTextBoxBinding = cellTextBox.GetBindingExpression(TextBox.TextProperty);
if (cellTextBoxBinding != null && !cellTextBoxBinding.ValidateWithoutUpdate())
{
cellTextBoxBinding.UpdateTarget();
}
}
}
Calling ValidateWithoutUpdate on the editing elements binding returns false if the conversion of the value fails, then calling the UpdateTarget forces the value to be reverted to the current 'model' value.

MVVM and (dynamically) filling a combobox from the value of another combobox

I have a form with two ComboBoxes. One of them is being filled with objects coming from a collection in the ViewModel. When I select a value in this ComboBox, it then should fill the second ComboBox.
What I want to know is what the best way is to go about filling the second ComboBox. I think having yet another collection with the details of the selected value of the first ComboBox in the ViewModel might be a bit wasteful. I think the best way might be to hit the database with the selected value, collecting the corresponding details, and then send them back. How I think this would work is to have the details ComboBox have a binding with the 'master' ComboBox so it can get the selected value. Then ideally, the details ComboBox would then somehow get the values from the database.
Problem is that I just don't know how to implement this with MVVM, and any help would be appreciated!
Just call OnPropertyChanged of the details collection once the selected item changes.
You can pre-populate a background dictionary whose key is the possible master items and whose values are a list of detail list.
Note for the below to work you ViewModel must implement INotifyPropertyChanged
e.g.
public class MyViewModel : INotifyPropertyChanged
{
public IEnumerable<MasterOption> MasterList {get;set;}
public IEnumerable<DetailOption> DetailList {get;set;}
Dictionary<MasterOption,List<DetailOption>> DetailLookup;
MasterOption _SelectedMasterOption;
public MasterOption SelectedMasterOption
{
get { return _SelectedMasterOption;}
set
{
_SelectedMasterOption = value;
LoadDetailsList();
OnPropertyChanged("SelectedMasterOption");
}
void LoadDetailsList()
{
InitDictionary();
if (DetailLookup.ContainsKey(SelectedMasterOption))
DetailList = DetailLookup[SelectedMasterOption];
else
DetailList = null;
OnPropertyChanged("DetailList");
}
void InitDictionary()
{
if (DetailLookup == null)
{
//Grab fill the lookup dictionary with information
}
}
}
Create a method in your ViewModel that gets the data for the second combobox and update with BindingExpression in your codebehind.
private void FirstComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
_viewModel.SelectionChange();
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(SecondComboBox, ComboBox.ItemsSourceProperty);
bindingExpression.UpdateTarget();
}

Resources