Working with ObservableCollections in WPF and MVVM - wpf

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.

Related

MVP (Model-View-Presenter) in VBA and returning a value

I am trying to implement the MVP pattern in VBA for a winform as I wish to be able to reuse the same code for the model/presenter but be able to change the view (winform) for another one easily. I think I have the basics sorted out, however, as the forms are more like "settings" forms rather than say, "interactive" ones, I wish to return a collection of values from the form when it closes, but I am not sure which part (M, V or P) to put this logic in.
I was thinking to put it in the presenter and set it up as a property that I could access from elsewhere.
Here's my code so far (please bear in mind I am a beginner with patterns, and this code is simplified it somewhat):
Presenter:
Private model As IPlanningParametersModel
Private view As IPlanningParameterView
Public Sub Initialise(view As IPlanningParameterView, model As IPlanningParametersModel)
Set model = model
Set view = view
End Sub
Public Sub updateViewWthModel()
Set view.PlanningParameters = model
End Sub
Public Sub updateModelWithView()
Set model = view.PlanningParameters
End Sub
Model:
Private m_ParamDictionary As Scripting.Dictionary
Implements IPlanningParametersModel
Private Sub Class_Initialize()
Set m_ParamDictionary = New Scripting.Dictionary
End Sub
Private Sub IPlanningParametersModel_Remove(ByRef Name As String)
Me.Remove Name
End Sub
Private Sub IPlanningParametersModel_Add(ByRef PlanParam As IPlanningParameter)
m_ParamDictionary.Add PlanParam
End Sub
Private Function IPlanningParametersModel_Item(ByRef Name As String) As IPlanningParameter
Set IPlanningParametersModel_Item = m_ParamDictionary.Item(Name)
End Function
View:
Implements IPlanningParameterView
Private Function IPlanningParameterView_Show(Optional ByVal Modal As Boolean = True)
If Modal Then Me.Show (vbModal) Else Me.Show (vbModeless)
End Function
Private Function IPlanningParameterView_Hide()
Me.Hide
End Function
Private Property Let IPlanningParameterView_Caption(ByRef Value As String)
Me.Caption = Value
End Property
Private Property Get IPlanningParameterView_PlanningParameters() As IPlanningParametersModel
'TODO: Cycle through each control in form to obtain configuration
' and add IPlanningParametersModel
End Property
Private Property Set IPlanningParameterView_PlanningParameters() As IPlanningParametersModel
'TODO: Cycle through each item in IPlanningParametersModel and set
' each control in form to reflect the configuration value
End Property
Finally the bit that connects them together:
Dim model As IPlanningParametersModel
Set model = New PlanningParametersModel
Dim view As IPlanningParameterView
Set view = New FPlanningParameterView
Dim pres As Presenter
Set pres = New Presenter
pres.Initialise view, model
So in this case I wish to actually use the values located in the model elsewhere in other code later on.
Should I add a new property to the Presenter part that just returns the model? E.g:
Public Property Get Settings() as IPlanningParametersModel
Set Settings = model
End Property
I've tried to search for a solution to this, but there aren't many examples of MVP in VB6/VBA (in fact, the only decent one I found was here), nearly all are in .NET which sometimes doesn't translate that well back to classic VB since they use features not available.
Edit:
After having more time to think and research on this, I think what I need is a way to obtain and set data in the model directly rather than using the storage object that the model is sat upon. For example, in most examples of MVP, the model is a "facade" for a database or other repository which stores its data somewhere. The other parts of the program (i.e. outside the MVP) then query this database to obtain the information that the model was sat on. In this case, nothing accesses the model directly, the flow goes via the database which is almost "independent " of the rest of the program.
In my particular case, I do not really need an underlying database to store this information as I just need to be able to set and get the values that the model holds.
Edit 2
Perhaps I could implement the "database" for the model as a Singleton class and then pass this to the model's constructor when it's initiated? For example:
Dim model As IPlanningParametersModel
Set model = New PlanningParametersModel
Set model.DataStore = MySingleton
Dim view As IPlanningParameterView
Set view = New FPlanningParameterView
Dim pres As Presenter
Set pres = New Presenter
pres.Initialise view, model
The DataStore property of the model could use an interface and MySingleton would implement the same interface and then I could then use MySingleton outside of the above code. Does that sound reasonable?
Any other suggestions on this structure are welcome as this is my first attempt!
The only problem with exposing the model from the presenter is that whatever is using the presenter becomes tightly coupled to it.
I prefer my presenters to emit events to an Event Aggregator that then publishes to any subscribers. This keeps everything loosely coupled.
Don't know how well this might work in VB6, it's been a long time. However, if you went down this road, your presenter would emit an event when the model changes with the current model attached to the event.

Update property's value in datagrid based on another property's input

I have an observable collection binded to a datagrid. On a datagrid row, when I edit a property's value I need another property on that row to update it's value based on a calculation. What is the best way to go about doing this. I tried creating a collectionchanged method like below:
Public WithEvents Tickets As ObservableCollection(Of Ticket)
Public Sub Tickets_CollectionChanged(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs) Handles TicketCollection.CollectionChanged
CalculatedVariable = determineCalculation()
End Sub
However this it only called when a new item is added or deleted, not when a property within the row is edited. Another way I looked at is calling the calculation method within the setter of each property but that seems messy knowing that i would need to call that method and update the property on several different properties that I need it on. Is there a good easy efficient way that I can accomplish what I'm trying to do? Thannks for any feedback :)
public property MyVariable
Get
return _MyVariable
Set(value)
_MyVariable = value
CalculatedVariable = determineCalculation()
RaisePropertyChange("MyVariable") , ect..
EndProperty
The class of the elements listed by your ObservableCollection has to implement INotifyPropertyChanged. You have to raise the PropertyChanged event in your setters. Then you can subscribe to this event inside your class and make the calculation in the callback. I find it a little bit cleaner than doing the calculation in each setter, and anyways you need to implement INotifyPropertyChanged if you want the UI to be updated when you change any property of your object.

Binding from multiple sources

My scenario:
I've got a Silverlight Application with a View, where i want to bind the textboxes to an object (two-way) and all labels to a dictionary holding the label translations.
My approach was to set the datacontext of the page to a dictionary with two items, one of them is the object and the other is the translation-dictionary.
In xaml the code looks like the following:
<TextBlock Text="{Binding [dict].[name],FallbackValue='Fallback'}" />
<TextBox Text="{Binding [obj].name,Mode=TwoWay}" />
This works initially, if I however change the object on the datacontext, the xaml is not notified about any changes and doesn't update.
I've had a working solution using a Converter for the translations, however due to the limitations on one converterparameter I didn't like the solution. In addition it wasn't possible to place a fallback-value in the textblock, which resulted in "invisible" textblocks while designing the page.
Any suggestions on how to solve this issue? It doesn't have to be using my dictionary, it would be also okay if i could set the datacontext to the object (which works) and bind the labels somehow different.
I know this will get a lot of traditional answers, but I would also like to put forward something completely original we tried (and succeeded) doing ourselves for more efficient localisation of Silverlight using Attached Properties instead of binding:
Localisation of Silverlight projects after completion
What would be the most flexible is rather than setting the DataContext for the view to a dictionary, you would be better off having the DataContext be something like a ViewModel. That is, a simple class that holds multiple properties: one for your "object" and one for your translation dictionary.
Then have the class that acts as your ViewModel implement INotifyPropertyChanged.
Create a method in your class called OnPropertyChanged that takes in a string representing your property name. In that method raise the PropertyChanged event passing in the instance of the ViewModel class and a new PropertyChangedEventArgs passing in the property name.
Back in the properties you created (object and dictionary) in the Set, after setting the value, call OnPropertyChanged passing in the string name of that property. This will notify the UI that the value of this property has changed and will essentially rebind the control to that property.
Finally, bind the Text properties of your controls on your View to the new properties you just created in your ViewModel. That should ensure that the controls on the view stay up to date.
I found a solution, but wasn't able to answer my own question (8h limit..)
I think this is just the approach Hydroslide suggested.
Create a class which holds all data and implements INotifyPropertyChanged
Public Class MyDatacontext
Implements ComponentModel.INotifyPropertyChanged
'Properties
Private _obj As Object
Private _dict As Dictionary(Of String, String)
'Events
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
'Methods
Public Property Obj As Object
Get
Return _obj
End Get
Set(ByVal value As Object)
_obj = value
'Notify the xaml about the changed Object
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Obj"))
End Set
End Property
Public Property Dict As Dictionary(Of String, String)
Get
Return _dict
End Get
Set(ByVal value As Dictionary(Of String, String))
_dict = value
'Notify the xaml about the changed translation
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Dict"))
End Set
End Property
End Class
Define a private var in your page code
Private mycontext As New MyDatacontext
In the constructor of your page, fill your "mycontext" with the desired data
mycontext.Dict = LoadDictionary()
mycontext.Obj = LoadObject()
Me.DataContext = mycontext
Change your xaml to the following
<TextBlock Text="{Binding Dict.[name],FallbackValue=MyFallback}" />
<TextBox Text="{Binding Obj.name,Mode=TwoWay}" />
Update your object/dictionary as you like using
mycontext.Obj = LoadNextObject()

WPF and VB.net: Data Binding to Separate Class created outside of Expression Blend

I have a WPF application with form that has a textbox named "txtStatusWindow". I also have a vb.net class handed to me by a co-worker that needs to be called and executed by the code in my application code-behind. My co-worker insists that I will need to use common .net events to update the textbox on my form.
The separate vb.net class:
Public Class globalclass
Public Event txtStatusWindow(ByVal Text As String)
Public Sub InitializeProgram()
RaiseEvent txtStatusWindow("Updating something.")
System.Threading.Thread.Sleep(2000)
RaiseEvent txtStatusWindow("Updating something else.")
System.Threading.Thread.Sleep(2000)
RaiseEvent txtStatusWindow("Updating something other than else.")
System.Threading.Thread.Sleep(2000)
RaiseEvent txtStatusWindow("Updating something other than the else stuff.")
System.Threading.Thread.Sleep(2000)
End Sub
End Class
I need to be able to call the sub "InitializeProgram()" from my code-behind, and it needs to be able to update "txtStatusWindow.text" as it runs.
I told him that the updating of the text box can be done with data-binding, but I don't know how to integrate a separate class like this into my project, how to call methods in it, or how to cause it to update my text blocks through data binding.
I also suggested that the methods in this class aren't optimal for connecting to the WPF project anyway, but he just wrote it as an example to discover how to connect the two projects.
Eventually, I will need to integrate classes like these that will be running separate threads to update their data from a dynamic source, and cause many controls to update in my application.
So far, the only way we have been able to get this to work from my code-behind is this:
Partial Public Class SplashScreen
Dim NewText as String
Public WithEvents Globals As globalclass = New globalclass
Public Delegate Sub StringDelegate(ByVal Text As String)
Public SplashText As String
Public Sub New()
MyBase.New()
Me.InitializeComponent()
Me.Show()
Globals.InitializeProgram()
End Sub
Public Sub UpdateSplashscreenHandler(ByVal Text As String) Handles Globals.UpdateSplashScreen
StatusWindowText.Text = Text
End Sub
Notwithstanding the fact that the WPF screen "freezes" until the "globalclass InitializeProgram" method completes (txtStatusWindow.Text does not update while sub without using the esoteric "refresh" extension...), I fully believe we are going about this the wrong way.
There are precious few examples out there concerning the integration and then binding to objects in existing code. Thanks for examining our little quandary.
If this status window is in XAML and the status window is a UserControl, then add a StatusText dependency property to the status window. Then, in the XAML you can bind to the value of that property with something like:
<UserControl x:Name="MyStatusWindow" ...>
<TextBlock Text="{Binding Path=StatusText, ElementName=MyStatusWindow}" />
</UserControl>
Then, from your event, just update the value of that StatusText property.
(Is that even close to what you were asking?)
Also, about that freezing: Instead of doing that updating in the constructor of that class, you might want to do it from the Loaded event of that control. It will still be freezing, though, unless you move it to a separate thread. Right now, that's happening on the same thread that the UI message pump is running on. This is the Dispatcher for that UI.

Is there an event that triggers if the number of ListViewItems in a ListView changes? (Windows Forms)

I'd like to enable/disable some other controls based on how many items are in my ListView control. I can't find any event that would do this, either on the ListView itself or on the ListViewItemCollection. Maybe there's a way to generically watch any collection in C# for changes?
I'd be happy with other events too, even ones that sometimes fire when the items don't change, but for example the ControlAdded and Layout events didn't work :(.
#Domenic
Not too sure, Never quite got that far in the thought process.
Another solution might be to extend ListView, and when adding and removing stuff, instead of calling .items.add, and items.remove, you call your other functions. It would still be possible to add and remove without events being raised, but with a little code review to make sure .items.add and .items.remove weren't called directly, it could work out quite well. Here's a little example. I only showed 1 Add function, but there are 6 you would have to implement, if you wanted to have use of all the available add functions. There's also .AddRange, and .Clear that you might want to take a look at.
Public Class MonitoredListView
Inherits ListView
Public Event ItemAdded()
Public Event ItemRemoved()
Public Sub New()
MyBase.New()
End Sub
Public Function AddItem(ByVal Text As String) As ListViewItem
RaiseEvent ItemAdded()
MyBase.Items.Add(Text)
End Function
Public Sub RemoveItem(ByVal Item As ListViewItem)
RaiseEvent ItemRemoved()
MyBase.Items.Remove(Item)
End Sub
End Class
I can't find any events that you could use. Perhaps you could subclass ListViewItemCollection, and raise your own event when something is added, with code similar to this.
Public Class MyListViewItemCollection
Inherits ListView.ListViewItemCollection
Public Event ItemAdded(ByVal Item As ListViewItem)
Sub New(ByVal owner As ListView)
MyBase.New(owner)
End Sub
Public Overrides Function Add(ByVal value As System.Windows.Forms.ListViewItem) As System.Windows.Forms.ListViewItem
Dim Item As ListViewItem
Item = MyBase.Add(value)
RaiseEvent ItemAdded(Item)
Return Item
End Function
End Class
I think the best thing that you can do here is to subclass ListView and provide the events that you want.

Resources