Using a MVP Pattern in a WinForms app I have been asked to write. Pardon VB.net as I am being forced to use this :(
Being New to MVP I have gone with a Passive Model implementation where there is no dependency between the View & the Model and only the Presenter knows both
The View being a representation of the UI what functionality should be part of the IVIEW interface
Should I have the methods/actions/tasks in the IView i.e
Property QItems As IList(Of QItem)
Property SelectedQItem As QItem
Property QueStatus As QueStatus
Property ReportName As String
Property ScheduleName As String
Sub BuildQItems()
Sub RunQue()
Sub StopQue()
Sub CancelCurrent()
Sub PauseCurrent()
and make the calls view the Iview Interface that is implemented in the winform
class Winform
implements IView
Private Sub btnCreate_Click(sender As System.Object, e As System.EventArgs) Handles btnCreate.Click Implements IVIEW.Create
If (_presenter.CreateSchdule()) Then
MessageBox.Show("Sucessfully Created")
Close()
End If
End Sub
End Class
or Should I just hold the state
Property QItems As IList(Of QItem)
Property SelectedQItem As QItem
Property QueStatus As QueStatus
Property ReportName As String
Property ScheduleName As String
And make the calls directly to the Presenter which is part of the WinForm and not bother about the Iview intreface
i.e
_presenter.BuildItems()
_presenter.RunQue()
How do you weigh up when to do either approach when using MVP ?
If you are referring to the passive view approach then you should not try to call the presenter or to write business logic inside the view. Instead, the view should create an instance of the presenter passing a reference of itself. Login form example:
public LoginView() // the Form constructor
{
m_loginPresenter = new LoginPresenter(this);
}
public void ShowLoginFailedMessage(string message)
{
lblLoginResult.Text = message;
}
The View interface should contain properties that allow the presenter to present business objects to the view as well as to manage the UI state (indirectly). Ex:
interface ILoginView
{
event Action AuthenticateUser;
string Username { get; }
string Password { get; }
string LoginResultMessage { set; }
}
The presenter would be something like:
public LoginPresenter(ILoginView view)
{
m_view = view;
m_view.AuthenticateUser += new Action(AuthenticateUser);
}
private void AuthenticateUser()
{
string username = m_view.Username;
...
m_view.ShowLoginResultMessage = "Login failed...";
}
Sorry about the C# code but I haven't touched VB.NET for a while now.
Related
I'm in a scenario to reuse a view with two completly independent view models.
For example you can think a generic list view to show apples somewhere and somewhere else to show cars. Doesn't really matter.
In Prism.Forms for Xamarin im able to glue a view with a viewModel like this.
Container.RegisterTypeForNavigation<PageA, ViewModelA>("PageA1");
Container.RegisterTypeForNavigation<PageA, ViewModelB>("PageA2");
I can't find an equivalent in Prism WPF, can someone help me out?
The link that #AdamVincent posted and the "missing" methods are very useful for normal view/viewmodel navigation using the ViewModelLocationProvider. However when trying to use two view models for the same view they don't work. This is because inside the extension method there is a call that registers the viewmodel to the view for use by the ViewModelLocationProvider.
private static IUnityContainer RegisterTypeForNavigationWithViewModel<TViewModel>(this IUnityContainer container, Type viewType, string name)
{
if (string.IsNullOrWhiteSpace(name))
name = viewType.Name;
ViewModelLocationProvider.Register(viewType.ToString(), typeof(TViewModel));
return container.RegisterTypeForNavigation(viewType, name);
}
Internally, ViewModelLocationProvider.Register uses a dictionary to store the association between view models and views. This means, whe you register two view models to the same view, the second will overwrite the first.
Container.RegisterTypeForNavigation<PageA, ViewModelA>("PageA1");
Container.RegisterTypeForNavigation<PageA, ViewModelB>("PageA2");
So with the above methods, when using the ViewModelLocationProvider, it will always create an instance of ViewModelB because it was the last one to be registered.
Additionally, the next line calls RegisterTypeForNavigation which itself ultimately calls Container.RegisterType, is only passing the viewType.
To resolve this, I tackled it a different way using an Injection property. I have the following method to bind my viewmodel to my view
private void BindViewModelToView<TView,TViewModel>(string name)
{
if (!Container.IsRegistered<TViewModel>())
{
Container.RegisterType<TViewModel>();
}
Container.RegisterType<TView, TViewModel>(name,new InjectionProperty("DataContext", new ResolvedParameter<TViewModel>()));
}
We know each view will have a DataContext property, so the Injection property will inject the viewmodel directly into the DataContect for the view.
When registering the viewmodels, instead of using RegisterTypeForNavigation, you would use the following calls:
BindViewModelToView<PageA,ViewModelA>("ViewModelA");
BindViewModelToView<PageA,ViewModelB>("ViewModelB");
To create the view, I already have a method that I use to inject the appropriate view into my region, and it works using the viewname as the key to obtain the correct viewmodel instance.
private object LoadViewIntoRegion<TViewType>(IRegion region, string name)
{
object view = region.GetView(name);
if (view == null)
{
view = _container.Resolve<TViewType>(name);
if (view is null)
{
view = _container.Resolve<TViewType>();
}
region.Add(view, name);
}
return view;
}
Which I simply call with
var view = LoadViewintoRegion<PageA>(region,"ViewModelA");
and
var view = LoadViewintoRegion<PageA>(region,"ViewModelB");
So for normal single View/Viewmodels, I use the ViewModelLocationProvider.AutoWireViewModel property and where I have multiple viewmodels, I use this alternative approach.
2022/01/15 Update
First of all thanks a lot for Jason's answer, his answer is great, and I implemented it perfectly with reference to his design, but because of Prism version update, I made some changes
I have an single view with multiple viewmodel
Step 1
register your view region
<ContentControl prism:RegionManager.RegionName="{x:Static hard:RegionNames.PanelPosCameraRegion}"></ContentControl>
Step 2
coding your viewmodel
private IRegionManager _RegionManager;
private IUnityContainer _UnityContainer;
public ICommand LoadedCommand { get; set; }
public RoboticPageVM(IRegionManager regionManager, IUnityContainer unityContainer)
{
_RegionManager = regionManager;
_UnityContainer = unityContainer;
LoadedCommand = new DelegateCommand(LoadedCommandHandle);
}
private void LoadedCommandHandle()
{
BindViewModelToView<PanelPosMultiplexView, PanelPosCameraVM>("Camera");
BindViewModelToView<PanelPosMultiplexView, PanelPosAxisVM>("Axis");
LoadViewIntoRegion<PanelPosMultiplexView>(RegionNames.PanelPosCameraRegion, "Camera");
LoadViewIntoRegion<PanelPosMultiplexView>(RegionNames.PanelPosAxisRegion, "Axis");
}
private void BindViewModelToView<TView, TViewModel>(string registerName)
{
if (!_UnityContainer.IsRegistered<TViewModel>())
{
_UnityContainer.RegisterType<TViewModel>();
}
_UnityContainer.RegisterType<TView>(registerName, new InjectionProperty(nameof(UserControl.DataContext), new ResolvedParameter<TViewModel>()));
}
private void LoadViewIntoRegion<TView>(string regionName, string registerName)
{
IRegion region = _RegionManager.Regions[regionName];
object? view = region.GetView(registerName);
if (view == null)
{
view = _UnityContainer.Resolve<TView>(registerName);
}
if (!region.Views.Any(v => v.GetType() == typeof(TView)))
{
region.Add(view, registerName);
}
}
Short version:
If I have ViewModel, containing its Model object and exposing its properties, how do I get the model "back" after it has been edited? If the Model-inside-ViewModel is public, it violates encapsulation, and if it is private, I cannot get it (right?).
Longer version:
I am implementing a part of an application which displays collections of objects. Let's say the objects are of type Gizmo, which is declared in the Model layer, and simply holds properties and handle its own serialization/deserialization.
In the Model layer, I have a Repository<T> class, which I use to handle collections of MasterGizmo and DetailGizmo. One of the properties of this repository class is an IEnumerable<T> Items { get; } where T will be some of the Gizmo subtype.
Now since Gizmo doesn't implement INPC, I have created the following classes in ViewModel layer:
GizmoViewModel, which wraps every public property of a Gizmo so that setting any property raises PropertyChanged accordingly;
[**] RepositoryViewModel<T>, which has an ObservableCollection<GizmoViewModel> whose CollectionChanged is listened to by a method that handles Adds, Removes and Updates to the repository.
Notice that the Model layer has a "Repository of Models", while the ViewModel layer has a "ViewModel with an ObservableCollection of ViewModels".
The doubt is related to the [**] part above. My RepositoryViewModel.CollectionChangedHandler method is as follows:
void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var added in e.NewItems)
{
var gvm = added as GizmoViewModel;
if (gvm != null)
{
//// IS ANY OF THE ALTERNATIVES BELOW THE RIGHT ONE?
// Gizmo g = gvm.RetrieveModel(); ?? proper getter ??
// Gizmo g = GetModelFromViewModel(gvm); ?? external getter ??
// Gizmo g = gvm.Model; ?? public model property ??
_gizmo_repository.Add(g);
}
}
break;
....
Besides that, if anyone can detect any MVVM smell here, I'll be happy to know.
We can deal with our Models even outside the View and ViewModel layers, so leaving the model publicly accessible from ViewModel is I believe acceptable.
Let say you are creating the Models in "DataLayer" you can pass the instance of the Model to the ViewModel. To illustrate my point:
///Models ////////////////////////////
public interface IGizmo{}
public class Gizmo:IGizmo{}
public class SuperGizmo : IGizmo {}
public class SuperDuperGizmo : IGizmo { }
//////////////////////////////////////
public interface IGizmoViewModel<out T>
{
T GetModel();
}
public abstract class GizmoViewModelBase : IGizmoViewModel<IGizmo>
{
protected GizmoViewModelBase(IGizmo model)
{
_Model = model;
}
private readonly IGizmo _Model;
public IGizmo GetModel()
{
return _Model;
}
}
public class GizmoViewModel : GizmoViewModelBase
{
public GizmoViewModel(Gizmo model)
: base(model) { }
}
public class SuperDuperGizmoViewModel : GizmoViewModelBase
{
public SuperDuperGizmoViewModel(SuperDuperGizmo model)
: base(model){}
}
Your repository of Models will be updated on whatever updates it get from the ViewModel as long as you passed the same instance. So there is no need to have a repository of ViewModels to get the updates.
Reading your code, I think there is something of a mixup regarding your ViewModel and Model separation.
So, as I understand it, when your ObservableCollection of GizmoViewModel's changes, you are trying to add the Gizmo instance of the new item back to your Model?
I would approach this differently. You should create your Gizmo instances inside your Model layer, and when you do this you should add it to the Repository.
Otherwise, you haven't provided enough information - or rather, you have provided too much but it is the wrong sort of information. You need to describe the situation in which you want to do this, where these GizmoViewModels are created, etc.
From what I can see here, your GizmoViewModel has a dependency to your Repository<T>, so why not pass in the repository when you create your view model?
public class GizmoViewModel
{
private IRepository<Gizmo> _Repo;
//Underlying model (Doesn't implement INotifyPropertyChanged)
private Gizmo _Model;
//Wrapping properties
public int MyProperty
{
get { return _Model.Property; }
set
{
_Model.Property = value;
NotifyOfPropertyChange();
}
}
...
public GizmoViewModel(IRepository<Gizmo> repo)
{
_Repo = repo;
}
public void AddToRepo()
{
_Repo.Add(_Model);
}
...
It would be even better if these methods are inside the RepositoryViewModel base class. You can really go crazy with inheritance here. Perhaps something like this:
var gvm = added as IRepositoryViewModel;
if (gvm != null)
gvm.AddToRepo();
You can then simply call AddToRepo when you need to add the view model's underlying model to the repository.
Perhaps not the most elegant solution, however if encapsulation is what's worrying you, then you need to ensure that your dependencies are properly managed.
"If the Model-inside-ViewModel is public, it violates encapsulation"
Your assertion above is completely wrong and is killing your code.
By setting the Model property in ViewModel as private, you are forced to repeat your self ( code smells ), as you will need to define in your ViewModel, the same properties as you did for your Model, effectively transforming it into a Model class that mimics the Model it is supposed to expose to the View.
In MVVM the ViewModel role is to provide the View with all the presentation data and logic that it needs and for sure the Model is fundamental part of this data, by hidding it from the View you are killing MVVM.
I am having difficulty loading some child attributes of a given entity. I have managed to load child entities on other objects, but not this one. To add to the frustration, the child entities I am trying to load are referenced from another Entity, and from this they work fine...
My code is as follows.
ViewWasteApplication Page
Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs)
If NavigationContext.QueryString.ContainsKey("ID") Then
' load an existing record - edit mode
Context.Load(Context.GetWasteApplicationsByIDQuery(Int32.Parse(NavigationContext.QueryString("ID").ToString())),
AddressOf wasteApplicationLoaded, Nothing)
Else
MessageBox.Show("Application not found")
End If
End Sub
This calls GetWasteApplicationsByID - which is as follows:
Public Function GetWasteApplicationsByID(ByVal query As Int32) As IQueryable(Of WasteApplication)
Dim result = Me.ObjectContext.WasteApplications.Include("CurrentlyWith") _
.Include("Packaging") _
.Include("WasteStream") _
.Where(Function(f) f.ID = query)
Return result
End Function
The WasteApplication is being returned, but neither of the 3 child entities are appearing.
I have created a MetaData class for this WasteApplication, as follows:
<MetadataTypeAttribute(GetType(WasteApplications.WasteApplicationsMetaData))> _
Partial Public Class WasteApplications
Friend NotInheritable Class WasteApplicationsMetaData
'Metadata classes are not meant to be instantiated.
Private Sub New()
MyBase.New()
End Sub
Public Property ID As Integer
Public Property RequestedByName As String
Public Property RequestedByExtension As String
Public Property CARQRequired As Boolean
Public Property OriginOfMaterialID As Integer
Public Property Comments As String
Public Property MaterialName As String
Public Property PackagingID As Integer
Public Property FacilityPath As String
Public Property ProcessStatus As String
Public Property DateSubmitted As DateTime
Public Property RequestedByUsername As String
Public Property CurrentlyWithID As Integer
Public Property WasteStreamID As Integer
<Include()>
Public Property WasteStream As WasteStreams
<Include()>
Public Property Packaging As Packaging
End Class
End Class
Can anyone see anything wrong with this? I load the same two child objects on another page, and these seem to load just fine. The code for this is as follows:
View Chemical Application (This works)
Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs)
Context.Load(Context.GetChemicalApplicationsByIDQuery(Int32.Parse(NavigationContext.QueryString("ID"))),
AddressOf wasteApplicationLoaded, Nothing)
End Sub
With the RIA function as follows:
Public Function GetChemicalApplicationsByID(ByVal query As Int32) As IQueryable(Of ChemicalApplication)
Return Me.ObjectContext.ChemicalApplications.Include("Chemical") _
.Include("ProcessStatus") _
.Include("PlanningApprover") _
.Include("FacilitiesApprover") _
.Include("MaintenanceApprover") _
.Include("PPCPermit") _
.Include("DischargeConsent") _
.Include("Facility") _
.Include("Packaging") _
.Include("WasteStream") _
.Where(Function(f) f.ID = query)
End Function
Any suggestions?
NOTE: I have not posted any of the XAML bindings, as I have confirmed via debugging that the source entities do not contain the child data, so therefore binding will not be an issue.
I am using Silverlight 4 with Entity Framework 4.
You will need to create metadata classes for the entities you wish to include and mark the fields with the [Include] attribute.
[MetadataTypeAttribute(typeof(Client.ClientMetadata))]
public partial class Client
{
internal sealed class ClientMetadata
{
private ClientMetadata()
{
}
[Required(ErrorMessage="You must enter a client name")]
public string Name;
[Include]
public EntityCollection<Contact> Contacts;
[Include]
public Employee Employee;
[Include]
public BillTo BillTo;
}
}
See RIA Services and relational data for more.
You need to do the 2 following things in order for this to work:
a) Add the include parameter in a metadata class (as DaveB proposed)
b) on the domain service queries (server side) add the include directive - Me.ObjectContext.WasteApplications.Include("CurrentlyWith")
I managed to solve the issue, you need to load the respective entities into the datacontext as well as loading the linked entity. i.e in my case I need to add:
Context.Load(Context.GetWasteStreamsQuery())
Context.Load(Context.GetPackagingQuery())
so that the linked child entities exist. My full onNavigate therefore looks like this:
Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs)
If NavigationContext.QueryString.ContainsKey("ID") Then
Context.Load(Context.GetWasteApplicationsByIDQuery(Int32.Parse(NavigationContext.QueryString("ID").ToString())),
AddressOf wasteApplicationLoaded, Nothing)
Context.Load(Context.GetWasteStreamsQuery())
Context.Load(Context.GetPackagingQuery())
Else
MessageBox.Show("Application not found")
End If
End Sub
I use prism v4 and MEF to load my modules. My modules contain a handful of views (MVVM) which are loaded in a ItemsControl/NavigationRegion automatically by MEF.
This works nicely, all items show up in the ItemControl. But I don't like the order in which they show. One module might contain several of the items, so changing the module load order is not enough by itself.
How can I sort the different views in the ItemsControl? Is there any way to sort them by some property?
I use prism V4, MEF and exploration due to attributes like in the StockTraderRI example.
This is actually baked into Prism4. Just apply the ViewSortHintAttribute to your views:
[ViewSortHint("100")]
class FirstView : UserControl { }
[ViewSortHint("200")]
class SecondView : UserControl { }
The default sort comparer on the regions will pick up this attribute and sort the views accordingly. You can put any string into the attribute but I tend to use medium sized numbers that allow me to easily put a new view in between existing ones.
Oh dang, this was way easier than I expected:
You can tell the region manager how to sort the views in a specific region. You just need to provide a compare function to the region.
This example sorts by a very stupid value, the function name:
private static int CompareViews(object x, object y)
{
return String.Compare(x.ToString(), y.ToString());
}
this._regionManager.Regions["MyRegion"].SortComparison = CompareViews;
Of course the region needs to be known to the region manager before you can set the SortComparison. So far the only workaround I found to achieve this was to defer to set the comparison function using the Dispatcher:
private readonly IRegionManager _regionManager;
[ImportingConstructor]
public ShellViewModel(IRegionManager regionManager)
{
this._regionManager = regionManager;
Dispatcher dp = Dispatcher.CurrentDispatcher;
dp.BeginInvoke(DispatcherPriority.ApplicationIdle, new ThreadStart(delegate
{
if (this._regionManager.Regions.ContainsRegionWithName("MyRegion"))
this._regionManager.Regions["MyRegion"].SortComparison = CompareViews;
}));
}
Of course one should use some more useful information than the class name for the sorting order, but this should be easy to solve (I'll just add an interface to all views which might be added to this region which provide a value to sort by).
I'm pretty sure you are looking for the CollectionViewSource. Bea provides some information on how to make use of it in the link.
From an MVVM stance this is how I use the ICollectionView within my ViewModel. The _scriptService.Scripts property is an ObservableCollection<T> getting wrapped in an ICollectionView which is returned to the View. The _view.Filter is being used to filter out items within the ICollection, thus changing the View. Similar to typing 'acc' and seeing all items that begin with 'acc' in your list.
public class ScriptRepositoryViewModel : AViewModel
{
private readonly IUnityContainer _container;
private readonly IScriptService _scriptService;
private readonly IEventAggregator _eventAggregator;
private ICollectionView _view;
public ScriptRepositoryViewModel(IUnityContainer container, IScriptService scriptService, IEventAggregator eventAggregator)
{
_container = container;
_scriptService = scriptService;
_eventAggregator = eventAggregator;
}
public ICollectionView Scripts
{
get
{
if (_view == null)
{
_view = CollectionViewSource.GetDefaultView(_scriptService.Scripts);
_view.Filter = Filter;
}
return _view;
}
}
}
Below is the code which takes care of the filtering, and is coming in via a DelegateCommand within Prism, this resides in the same ViewModel.
#region SearchCommand
public DelegateCommand<object> SearchCommand { get; private set; }
private String _search = String.Empty;
private void Search(object commandArg)
{
_search = commandArg as String;
_view.Refresh();
}
public bool Filter(object arg)
{
bool usingPrefix;
IScript script = arg as IScript;
if (script.FileType == ConvertPrefixToFileType(_search, out usingPrefix))
{
if (_search.Length == 2)
return true;
else
return CheckProperties(script, usingPrefix);
}
else
{
if (usingPrefix)
return false;
else
return CheckProperties(script, usingPrefix);
}
}
With the base functionality in place and making use of the ICollectionView you can apply your sorting as follows....
_view.SortDescriptions.Add(new SortDescription("PropertyName", direction));
More information on the sorting behavior can be found here, as there are some performance thoughts to keep in mind.
You could use either metadata or properties. It depends on whether you have control over the interface or not...
Views are displayed in the order they are added:
RegionManager.RegisterViewWithRegion("ListRegion", typeof(ListView));
RegionManager.RegisterViewWithRegion("ListRegion", typeof(ListView2));
RegionManager.RegisterViewWithRegion("ListRegion", typeof(ListView3));
will look like:
----region--|
| view3 |
| view2 |
| view |
this is my scenario, and I want to know if it's possible to accomplish what I intend to do:
I have a class library (made in c#) that inside has a class (named SForm) that inherits from System.Windows.Forms.Form. In that class I declare some strings, and methods to set those strings' values.
public class SForm : Form
{
public string _myDate;
public void setTime(string val) { _mydate = val; }
}
In another class, I make some calls to an API that trigger callbacks, and when a callback occurs, call the methods to set the values in the class that inherits from Form. All working fine, and all classes are packed in a DLL.
public class Events
{
private SForm _form;
public void setForm(SForm f)
{
_form = f;
}
public void connect()
{
//when I call this method, connects to a device using the API, and if
//it's succesful, triggers OnCallback...
}
private void OnCallback(string retVal)
{
_form.setTime(retVal); //this works
}
}
Here is my problem: I have a desktop app, in VB, that uses that DLL, and when I inherit from SForm, I want that the callback triggered by the DLL invokes the method in my form
Public Class Form1
Inherits SForm
Private _se As Events
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.Load
_se = New Events()
_se.setForm(Me)
_se.connect()
End Sub
Private Overloads Sub setTime(ByVal s As String)
MessageBox.Show(s)
End Sub
What I need is that the callback trigger the "setTime" method in this form, and show the value sent by my DLL (I can push a button and access the value of the _myDate string, but I need it to be automatic). Is that possible? How??
Thanks a million
You need to declare setTime() virtual so you can override it. Don't forget to call the base method.