Multiple Instances of the same WPF Window and ViewModel - wpf

I have a window named 'winAppt.xaml' and a view model called 'ItemViewModel.vb'. I would like the user to be able to open multiple instances of the 'winAppt.xaml' window to show different accounts on the screen at once. Problem right now is that when the second instance of 'winAppt.xaml' loads the first instance has some of it's data replaced with the second instance.
I'm currently doing something like this
Dim i As New ItemViewModel()
i.Load(itemID)
Dim fDetailRec As New winAppt(i)
fDetailRec.ShowDialog()
I then set the DataContext of my window to the view model passed in.
Public Sub New(ByVal i As ItemViewModel)
Me.DataContext = i
End Sub

Found a shared reference to a class inside the view model. This class was declared in a module and persisted throughout the application. This class contained a list that I thought I was passing to my ViewModel, it was really referencing it.

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.

Double click listview item and pass the variable to another page to run a search

I have 2 forms; MainWindow and OwnerShares. There is a search on MainWindow that looks up what items are being shared. OwnerShares has a search function that looks up the owner details and what they are sharing. On OwnerShares the options are displayed in a ListView. What i want to do is allow the user to double click on a row in ListView and be directed to the MainWindow which runs the SEARCH based on the value that was selected from the ListView. The variable ACEName will be the one that i want to pass to the MainWindow form search.
Private Sub listSearchOwner_MouseDoubleClick(sender As Object, e As MouseButtonEventArgs) Handles listSearchOwner.MouseDoubleClick
Dim ACEName As String
ACEName = listSearchOwner.SelectedItems(0).xShareName
OwnerShares.Close()
End Sub
The name of the search in MainWindow is (cmdSearch_Click). I am currently getting the value that i click on with the above code. I just dont know how to open the MainWindow form and run the search command automatically.
If I read your question correctly you could just call show your form and run your other routine
Private Sub listSearchOwner_MouseDoubleClick(sender As Object, e As MouseButtonEventArgs) Handles listSearchOwner.MouseDoubleClick
Dim ACEName As String
ACEName = listSearchOwner.SelectedItems(0).xShareName
OwnerShares.Close()
MainWindow.show()
' YourMethod or Button here.
End Sub
Approach it this way:
Add a module to your project, if you don't have one.
Create a public variable in the module, e.g.
Public VariableName As DataType
Everytime that you double-click the listview control, assign a value to your variable.
After getting the value stored in the public variable, delete the value to create a room to pass another value again.
That should do the trick.

How do I resolve this apparent Catch22

I have a wpf custom control with (as is common) a shared constructor. This one looks like this;
Shared Sub New()
'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
'This style is defined in Themes\Generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(GetType(VtlDataNavigator), New FrameworkPropertyMetadata(GetType(VtlDataNavigator)))
ItemsSourceProperty.OverrideMetadata(GetType(VtlDataNavigator), New FrameworkPropertyMetadata(Nothing, AddressOf OnItemsSourceHasChanged))
End Sub
The control inherits ItemsControl and I want to have notification of when the itemsSource changes hence the second line in the constructor. OnItemsSourceHasChanged needs to be a shared sub in order for the line in the constructor to compile. Fine.
In the shared sub I have the following:
Private Shared Function OnItemsSourceHasChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) As Object
RecordCount = Items.SourceCollection.Cast(Of Object)().Count()
End Function
which of course fails to compile because you cannot refer to an instance method of a class from within a shared method without creating a specific instance of the class. Even If a create a separate non shared handler for this I will still end up with the same error when trying to call it.
In essence all I want is the total RecordCount from the ItemsSource (hence my need to know when it's changed) so that I can then assign that value to RecordCount. However what I appear to have instead is the proverbial catch 22 of errors.
The answer is probably staring me in the face, but for now it escapes me. Any Ideas?

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.

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.

Resources