I’m currently working on a WPF MVVM application using MVVM Light as the MVVM Framework, Entity Framework as the ORM, and MS Synch Framework as the means of synchronizing a Local Sql Compact DB with an online SQL database. The application is also fairly complex in scope, as it is meant to manage an asset, calculating wear and tear on the use of that asset through its lifetime. Thankfully I’m new to all these technologies so ignorance is bliss :) I’ve found lots of tutorials and information on creating the Unit of Work Patter and Repository pattern. However, I’m using the new DbContext, which I’ve read already uses these two patterns.
My current issue relates to using the new DbContext in Entity Framework. I’ve used DbContext Generator template in VS, and so I have a MyDbModelContainer. I’ve used this directly from my view models, which creates a Context per VM, which I’m pretty sure is a bad idea. Here is my constructor for a Parent/Child type data entry scenario, I construct the container here, and them use MVVM Light to message over a selected item to the child VM.
Private FatigueDbContext As FatigueModelContainer
Public Sub New()
If IsInDesignMode = False Then
FatigueDbContext = New FatigueModelContainer
FatigueDbContext.CtMaterials.Load()
CtMaterialsCollection = FatigueDbContext.CtMaterials.Local
FatigueDbContext.CtManufactures.Load()
CtManufactures = FatigueDbContext.CtManufactures.Local
End If
End Sub
I want to keep the Context open for the lifetime of my View-Model so that I can use MyDbModelContainer.MyTable.Local for bindings. So while this is working, how should I handle this correctly?
My gut feeling is I need to somehow wrap the auto-generated MyDbModelContainer into some classes that basically only contain the tables and functions that I need to work with on that View-Model.
I’m not using a View-Model Locator, but rather another View-Model to manage my views, got the idea from Rachel's blog , and I like the concept. However, it means that I’m creating all my View-Models up front, and initializing any of the view model dependencies up front. I only have one window, and am just switching between View-Models they stay in memory and I don’t have any way to close my DbContext when switching to a new View-Model.
Here is the code for the Shell View-Model
Public Class ShellViewModel
Inherits ViewModelBase
#Region "Fields"
Private _changePageCommand As ICommand
Private _currentPageViewModel As IPageViewModel
Private _pageViewModels As List(Of IPageViewModel)
#End Region
Public Sub New()
Dim DialogService As New ModalDialogService
' Add available pages
PageViewModels.Add(New ImportJobDataViewModel(DialogService))
PageViewModels.Add(New TestViewModel())
PageViewModels.Add(New ReverseBendUtilityViewModel(DialogService))
' Set starting page
CurrentPageViewModel = PageViewModels(0)
End Sub
#Region "Properties / Commands"
Public ReadOnly Property ChangePageCommand() As ICommand
Get
If _changePageCommand Is Nothing Then
_changePageCommand = New RelayCommand(Of IPageViewModel)(Sub(param) ChangeViewModel(param))
End If
Return _changePageCommand
End Get
End Property
Private Function IsViewPageModel(viewPageModel As IPageViewModel) As Boolean
If TypeOf (viewPageModel) Is IPageViewModel Then
Return True
Else
Return False
End If
End Function
Public ReadOnly Property PageViewModels() As List(Of IPageViewModel)
Get
If _pageViewModels Is Nothing Then
_pageViewModels = New List(Of IPageViewModel)()
End If
Return _pageViewModels
End Get
End Property
Public Property CurrentPageViewModel() As IPageViewModel
Get
Return _currentPageViewModel
End Get
Set(value As IPageViewModel)
If _currentPageViewModel IsNot value Then
_currentPageViewModel = value
RaisePropertyChanged(Function() CurrentPageViewModel)
End If
End Set
End Property
#End Region
#Region "Methods"
Private Sub ChangeViewModel(viewModel As IPageViewModel)
If Not PageViewModels.Contains(viewModel) Then
PageViewModels.Add(viewModel)
End If
CurrentPageViewModel = PageViewModels.FirstOrDefault(Function(vm) vm Is viewModel)
End Sub
#End Region
End Class
So to sum it all up... Should I be creating some separate class aside from the auto-generated FatigueModelContainer, what would that class look like, would it be just one more class, or would it be separate classes based on the business operations. Such as a class to Add, Update and Delete Manufactures, a class to Add, Update and Delete Materials, etc. Where should I be inserting it into the VM? Presumably in the Shell-View-Model?
You can solve this problem without really touching your db context. The idea is that even if the VM stays in memory, you only need the model instance to be initialized when the VM's view is active.
There is a nice way of going about this in Caliburn.Micro: Screens and Conductors, but it's really not specific to the mvvm framework.
In the most basic case, as Rachel mentioned in the comment above, you extend your IPageViewModel to include functions which handle activation- and deactivation-logic for the VM: activation logic is called when the VM is activated, deactivation logic is called when the VM is deactivated.
The actual call to activate/deactivate would be made in the container for the IPageViewModels, for instance:
Public Property CurrentPageViewModel() As IPageViewModel
Get
Return _currentPageViewModel
End Get
Set(value As IPageViewModel)
If _currentPageViewModel Is value Then
Return
End If
If _currentPageViewModel IsNot Nothing Then
_currentPageViewModel.Deactivate()
End If
_currentPageViewModel = value
If value IsNot Nothing Then
value.Activate()
End If
RaisePropertyChanged(Function() CurrentPageViewModel)
End Set
End Property
That way you can close the db connection/detach the DbContext when a page is deactivated and open it when the page is activated.
Related
I am trying to achieve the following in a WPF personal finance app:
In various places I want to display a user control giving details of a asset holding (usually a share, bond etc), the target asset may be changed dynamically by the user in which case the control must be refreshed. Each Asset has a unique identifier, AssetId.
I am using MVVM and I've developed a single window with a View Model that takes AssetID as a parameter (property) and retrieves the relevant details for binding to the View. This work fine. What I'd like to do is make a generic user control with the same functionality so I can basically drop that 'window' inside other windows.
So I pretty much copy-pasted the XAML from that form into a User Control, where I'm struggling is passing in the AssetId from the parent window to the child control.
Google tells me I need a dependency property and here's where I am
Public Class HoldingView
Private _AssetId As Integer
Public AssetIdProperty As DependencyProperty = DependencyProperty.Register("AssetId",
GetType(Integer),
GetType(HoldingView),
New FrameworkPropertyMetadata(New PropertyChangedCallback(AddressOf AssetIDChanged)))
Public Property AssetId As Integer
Get
Return GetValue(AssetIdProperty)
End Get
Set(value As Integer)
SetValue(AssetIdProperty, value)
End Set
End Property
Private Sub AssetIDChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim NewAssetId As Integer
NewAssetId = e.NewValue
Me.DataContext.AssetId = NewAssetId
End Sub
Public Sub New()
' This call is required by the designer.
InitializeComponent()
Me.DataContext = New HoldingViewmodel
End Sub
End Class
Called like this:
<Grid>
<local:HoldingView AssetId="{Binding AssetId}"/>
</Grid>
The code compiles and runs but when I try and load the window that has the user control, the app crashes with this message:
A 'Binding' cannot be set on the 'AssetId' property of type 'HoldingView'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Which is not that helpful. From my Googling, you can also get this message if the syntax of the DP registration is not spot on, but it looks Ok to my inexperienced eye...
Anybody else had this?
Public AssetIdProperty As DependencyProperty
should be
Public Shared ReadOnly AssetIdProperty As DependencyProperty
Please take a look at Custom Dependency Properties.
Also remove
Me.DataContext = New HoldingViewmodel
because that will effectively break any DataContext-based Bindings like
AssetId="{Binding AssetId}"
where the source property is supposed to be owned by the object in the inherited DataContext, which usually is an object in the application's view model.
Controls should never have their own, "private" view model, but instead handle property changes in code behind. In case of UserControls, there could simply be UI elements in their XAML that would be bound to the UserConrol's own properties.
Hence
Me.DataContext.AssetId = NewAssetId
in the PropertyChangedCallback is pointless and should be removed, as well as
Private _AssetId As Integer
To summarize, it should look like this:
Public Class HoldingView
Public Shared ReadOnly AssetIdProperty As DependencyProperty =
DependencyProperty.Register(
"AssetId",
GetType(Integer),
GetType(HoldingView),
New FrameworkPropertyMetadata(
New PropertyChangedCallback(AddressOf AssetIdPropertyChanged)))
Public Property AssetId As Integer
Get
Return GetValue(AssetIdProperty)
End Get
Set(value As Integer)
SetValue(AssetIdProperty, value)
End Set
End Property
Private Shared Sub AssetIdPropertyChanged(
d As DependencyObject, e As DependencyPropertyChangedEventArgs)
CType(d, HoldingView).AssetIdChanged(e.NewValue)
End Sub
Private Sub AssetIdChanged(id As Integer)
...
End Sub
Public Sub New()
InitializeComponent()
End Sub
End Class
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.
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.
I'm trying to create a UserControl which consists of a DataGrid and a couple of buttons. The buttons will handle adding/deleting rows (needs to be buttons). The DataGrid is bound to a custom observable collection. The collections properties will vary (so I'm auto-generating the columns).
How can I add a new row? Normally I'd just modify the observable collection. I've tried adding a new row directly to the control:
dgMain.Items.Add(New DataGridRow())
but I get an error which doesn't mean much to me:
Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
Here's the current code behind:
Public Class DataGrid
Sub New()
InitializeComponent()
End Sub
#Region "Dependency Properties"
Public Shared MyItemsSourceProperty As DependencyProperty = DependencyProperty.Register("MyItemsSource", GetType(IEnumerable), GetType(DataGrid))
Public Property MyItemsSource() As IEnumerable
Get
Return DirectCast(GetValue(MyItemsSourceProperty), IEnumerable)
End Get
Set(value As IEnumerable)
SetValue(MyItemsSourceProperty, value)
End Set
End Property
#End Region
#Region "Buttons"
Private Sub btnAdd_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles btnAdd.Click
dgMain.Items.Add(New DataGridRow())
End Sub
#End Region
End Class
So does anyone know how I can add a new row?
Thanks for any help.
EDIT: This is how the data is created:
Dim np As New ObPerson
np.Add(New Person With {.FirstName = "Jane", .LastName = "Mitel", .Age = 18})
np.Add(New Person With {.FirstName = "Joe", .LastName = "Bloggs", .Age = 92})
UserControlInstance.MyItemsSource = np
Public Class ObPerson
Inherits ObservableCollection(Of Person)
End Class
EDIT2: VB Version of the accepted answer:
Public Shared Sub AddNewElement(l As IList)
If l Is Nothing OrElse l.Count = 0 Then
Throw New ArgumentNullException()
End If
Dim obj As Object = Activator.CreateInstance(l(0).[GetType]())
l.Add(obj)
End Sub
Usage: AddNewElement(MyItemsSource)
You need to use the collection that's bound - not the 'Items' property on the grid. ItemsSource will point to your collection that is bound:
SomeGrid.ItemsSource = SomeCollection;
SomeCollection.Add(new ItemOfTheRightType());
or
(SomeGrid.ItemsSource as SomeCollection).Add(new ItemOfTheRightType());
The error says that you can't use Grid.Items if you are binding using Grid.ItemsSource
Edit:
If you don't know the item type at runtime (maybe because this is a 3rd party using the control etc and you want a generic add method) you need to call the .Add method on the underlying interface. Most list types inherit from IList in the .NET framework
I'm no VB expert, I much prefer c# so I'll give you the c#. You need to check for the underlying type first:
in c#
if(grid.ItemsSource is IList)
{
(grid.ItemsSource as IList).Add(new childType()); <-- other issue here..
}
The problem you have though is that if you are adding a new item to the collection and you don't know the list type, IList requires an instance of the object to add to the list
solution is to use reflection:
Dynamically creating a new instance of IList's type
An interesting late answer is:
var collectionType = targetList.GetType().GetProperty("Item").PropertyType;
var constructor = collectionType.GetConstructor(Type.EmptyTypes);
var newInstance = constructor.Invoke(null);
Which might work
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.