Changing Combobox item in code-behind throws invalidoperationexception - wpf

I have several comboboxes which are initialiced with a default text which will be replaced later on by textes out of a text-file to support different languages.
I tried different ways to change the text but none of them worked out:
Initialization:
myCombobox.itemSource = new ObservableCollection(Of String){"FirstItem", "SecoundItem"})
First Way:
myCombobox.Items(i) = GetString(myCombobox.Items(i))
Secound Way:
Dim comboboxStr = myCombobox.Items(i)
myCombobox.Items.RemoveAt(i)
myCombobox.Items.Add(GetString(i))
Both ways throw an InvalidOperationException with the hint to try "ItemsControl.ItemsSource" instead of "ItemsSource"
Is there maybe another way to change the items or what why is this exception occurring?

This is a common problem. Once you have data bound the ItemsControl.ItemsSource (or any class derived from ItemsControl, then you cannot use the ItemsControl.Items property to manipulate the data. Instead, (you should declare a property and) just access the data collection that you data bound directly:
SomeProperty = new ObservableCollection(Of String){"FirstItem", "SecoundItem"})
...
myCombobox.itemSource = SomeProperty
...
SomeProperty.Remove(someItem)

Related

Working with ObservableCollections in WPF and MVVM

I'm fairly new to WPF and still try to get the feeling on how to do something with built-in functions rather than inventing the wheel on my own again.
Today I stumbled upon a problem, that I couldn't solve with built-in functions and the possible ways I could think of I didn't like very much. So hopefully you can point me in the right direction or even can name a clever way with built-in functions.
So, for the sake of simplicity let's say I'd like to write a ViewModel for the MailMessage class that can be found in the System.Net.Mail namespace.
Imports System.Collections.ObjectModel
Imports System.Net.Mail
Public Class MailMessageViewModel
Private _message As MailMessage
...
End Class
A MailMessage object has (among others) a property To of type MailAddressCollection containing all the recipients for my e-mail as MailAddress objects.
In my ViewModel I wrap this collection of MailAddress objects into an ObservableCollection.
And here's my first question, how do I do that. Do I use:
Public ReadOnly Property Recipients As ObservableCollection(Of MailAddress)
Get
Return New ObservableCollection(Of MailAddress)(_message.To)
End Get
End Property
or do I use:
Private _recipients As ObservableCollection(Of MailAddress)
Public ReadOnly Property Recipients As ObservableCollection(Of MailAddress)
Get
If _recipients Is Nothing Then
_recipients = New ObservableCollection(Of MailAddress)(_message.To)
End If
Return _recipients
End Get
End Property
My view model now has a bindable property Recipients.
Now I'd like to be able to delete an e-mail address from the To collection of my MailMessage.
But when I delete an address from the ObservableCollection, my UI gets updated properly, but the To collection stays untouched. If I delete directly from the To collection of my MailMessage, the ObservableCollection and therefore my UI don't reflect the changes.
Do I really have to wire the ObservableCollection and the corresponding source collection manually by using the CollectionChanged event or by doing all changes twice (in the ObservableCollection and in the real collection)? Or is there any clever WPF way I don't know of?
Don't "wrap" anything.
Simply create a View Model containing properties needed to send your mail message.
At some point in future, you'll actually be sending the message. For example, the user clicks a Send button that fires an ICommand somewhere. At this time, convert your ViewModel into a MailMessage.
You cannot "wrap" one collection within another without lots of code. It only takes a few minutes to copy property values from an instance of one type to an instance of another type.
If the changes always go from the ObservableCollection to the original List, i think that you could add a handler to 'CollectionChanged' event of the ObservableCollection. I think that doing it this way won't be so onerous.
AddHandler Recipients.CollectionChanged, AddressOf RecipientsCollChanged
....
Private Sub RecipientsCollChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
If e.OldItems IsNot Nothing Then
For Each elem In e.OldItems
_message.To.Remove(elem)
Next
End If
End Sub
Obviously, if you want, you can also handle the modify and the adding of elements into the ObservableCollection using the informations into the NotifyCollectionChangedEventArgs parameter.

How to make ViewModel aware of conversion error

I am designing a WPF application following MVVM. My ViewModel is exposing one Double property called DoubleValue, which is binding to a TextBox in the View. I have set "ValidatesOnDataErrors=True" for the binding. So if the user types a string which can't be converted to a Double, it display the red background.
In my ViewModel I also have a Command object, let's call SaveCommand, whose CanExecute delegate is depending on whether there is any error in the VM (my ViewModelBase class implements IDataErrorInfo, I have an overridable ValidatePropertyByName function and the validation errors are stored in a dictionary.) But now my problem is, if I give an invalid string in the TextBox, since the conversion fails, it never calls the setter of the binding property value. In another word, the ValidatePropertyByName is not called and the error dictionary remains the previous state, which normally is clean. So if now the user click the Save button (which is enabled since the error dictionary is clean), the SaveCommand executes with the previous valid double value to save. This is obviously not good.
So how can I make my ViewModel aware of such conversion errors?
UPDATE:
Some code example:
The binding property is like this:
Public Property DoubleValue As Double
Get
Return _doubleValue
End Get
Set(value As Double)
If value <> _doubleValue Then
_doubleValue = value
RaisePropertyChanged("DoubleValue")
End If
End Set
End Property
Private _doubleValue As Double
My binding is like this:
<TextBox Grid.Row="3" Text="{Binding DoubleValue, ValidatesOnDataErrors=True}" />
And now my problem is: if I give a string "XXX" in the text box, since it can't be converted to a double value, the setter of DoubleValue is never get called. And so the property value remains the previous(valid) value. Now if my SaveCommand gets executed, it will do the save operation with this previous valid value, which will make the user confused.
the most easy way is to just use string properties in your viewmodel. then you get all input from the user and can validate it in your viewmodel. the drawback is that you have to convert the values to the right type when you go to the model.
if you dont want this you have to create your own controls or better behaviors so that the use can just input values that your viewmodel expect. eg. NumericInputBehavior.
You cannot simply put these two things together. One is the regular validation inside the ViewModel. The other are control-specific problems, like unconvertible values.
So there are two possible ways to solve this:
1) Don't use a converter. Just bind the string. Inside the ViewModel you can then use the validation to check for a valid value. (More MVVM)
2) Store your ValidationErrors on the controlside and merge them with the viewmodel errors. This is not easy but a good way to create one source for binding against ALL problems within your UI. We are doing this for complex textboxes at work. This means manual code in the controls but for complex customcontrols this is OK, I believe.
edit: just to elaborate a little on the 2nd point. We are having a DependencyProperty of Type ObservableCollection inside the Control. Then you can bind this Collection to a ViewModel Property and as soon as your control moves an Error inside the collection it is available inside the viewModel. You can then use this collection inside your validation implementation. This works pretty well for larger controls.
Edit2: For the MarkInvalid Stuff I mentioned in the comment. It would look like this:
DataErrorValidationRule validationRule = new DataErrorValidationRule();
ValidationError validationError = new ValidationError(validationRule, myTextBox.GetBindingExpression(TextBox.TextProperty)) { ErrorContent = "My custom message" };
Validation.MarkInvalid(myTextBox.GetBindingExpression(TextBox.TextProperty), validationError);
You would call in from inside a TextChanged when you can't convert the new given value or
Validation.ClearInvalid(myTextBox.GetBindingExpression(TextBox.TextProperty))
Maybe that will help?

WPF updating parameterized bindings

I have a property in a class:
Default Public ReadOnly Property GetLiteral(Key As String) As String
Get
Try
Dim Row() As String = _LookupTable.Item(Key)
Return Row(_CurrentLanguage)
Catch ex As Exception
Return ("Error")
End Try
End Get
End Property
And I bind a label to it:
<Label Content="{Binding SLP.[LITERAL_USERNAME]}"/>
Where SLP is a public property in my view model containing an instance of the class containing the default property GetLiteral.
The above binding works fine. I can select different languages in my view. However, I wish to also be able to change languages on the fly, and I can't figure out how to raise INotifyPropertyChanged in order to make this binding update. I know I could probably achieve this easily with a value converter and parameter, but I like the simplicity of the XAML above.
Thanks.
I figured it out. I was calling NotifyPropertyChanged from inside SLP, where what I really needed to do was call NotifyPropertyChanged(Me, "SLP") from the View Model.

Observable Collection is not updating the datagrid

I am using a Dim All_PriceLists As System.Collections.ObjectModel.ObservableCollection(Of BSPLib.PriceLists.PriceListPrime) where PriceListPrime implements Inotify for all properties in it.
I bound the All_PriceList to a datagrid as DataGrid1.ItemsSource = All_PriceLists but when I do All_PriceLists=Getall() where Getall reads and gets the data from the DB, the datagrid is not updating.
It updates only when I hack it this way:
DataGrid1.ItemsSource = Nothing
DataGrid1.ItemsSource = All_PriceLists
Could you please tell me where I have gone wrong or what I should implement. Thank you.
You have several solutions to your problem
Update the ItemsSource directly (instead of replacing the local member variable)
DataGrid1.ItemsSource = new ObservableCollection(Of PriceListPrime)(GetAll())
Update the ObservableCollection (as mentioned in another answer)
All_PriceList.Clear();
For Each item in Getall()
All_PriceList.Add(item)
Next
Set your DataContext to a view model and bind to a property of the view model
Dim vm as new MyViewModel()
DataContext = vm
vm.Items = new ObservableCollection(Of PriceListPrime)(GetAll())
The view model will implement INotifyPropertyChanged and raised the PropertyChanged event when the Items property is changed. In the Xaml your DataGrid's ItemsSource will bind to the Items property.
The problem is that you are not updating the collection, you are replacing it, which is different.
The datagrid remains bound to the old list and the updated data is stored in a new unbound collection. So, you are not hacking a solution, your are binding the datagrid to the new collection, which is correct.
If you want a more automatic solution, you should bind your datagrid to a dataset/datatable, which is totally different code.
You should update ObservableCollection instead of creating new one if you want the app to react on your changes.
So, clear All_PriceList collection and add new items into it. Example:
All_PriceList.Clear();
For Each item in Getall()
All_PriceList.Add(item)
Next
ObservableCollection doesn't support AddRange, so you have to add items one by one or implement INotifyCollectionChanged in your own collection.

UltraWinGrid nested object properties binding with BindingSource

I am working on a winforms application where I am rendering domain/object data via an ultrawingrid. I am using a bindingsource to bind the object to the grid.For simple objects this works quite well.
The thing I'm trying to get my head around is rendering an object with nested objects for e.g a Person class will have a property of Address class. I would like to display the properties of Address (Street, City, Country) as columns on the grid along with the properties of the Person class.
The grid has to be editable and any user changes need to reflect back on the domain object (which I'm doing via the binding source).
What's the best way to go about this?
Binding
I typically use some sort of code like this:
Dim persons = new BindingList(Of Person)
UltraGrid1.DataSource = persons
The binding list will handle the add/remove of rows for you, but it doesn't know about the properties inside Person. To get that part of the binding to work, you'll need to have Person implement INotifyPropertyChanged. This will notify the ultragrid when the properties have changed. The code will look something like this (yes, unfortunately this makes it so you can't use auto-implemented properties):
Private _phoneNumber As String
Public Property PhoneNumber As String
Get
Return Me._phoneNumber
End Get
Set(ByVal value As String)
If value <> _phoneNumber Then
Me._phoneNumber = value
NotifyPropertyChanged("PhoneNumber")
End If
End Set
End Property
Flattening object hierarchies
It looks like what you're ask for isn't directly possible. There are a few options:
Unbound columns in the UI that you fill in during the InitializeRow event
Modify your Person class to expose the properties of Address with some pass through code to handle the setting of the properties.
(I can provide a code samples if needed)
One-to-many Nested Objects
If you, for example, had multiple addresses per person, you could show them nested in an expandable section under each Person row. To do this, inside your Person you'd have a BindingList(Of Address) which also implements INotifyPropertyChanged. Not exactly what you want, but an option :)
Words of caution
A few notes if you are doing MVP. You'll need to have the same reference to the BindingList in your view and presenter, obviously. Also, if you need to reset the contents, I'd recommend calling list.Clear() instead of creating a new one. If you create a new one in your presenter, you'll have broken the connection with the UltraGrid and you'll have to re-set the DataSource property in the view.

Resources