I hope you guys can help me out as I can't find anything useful that helps with the understanding of my problem:
I'm trying to realize a passive MVP approach on my C# WinForms application which has list views and corresponding detail views.
So far I've got the following structure (pseudo code):
ListPresenter(new Repository(), new ListView(), new DetailPresenter(new DetailView());
Implementation:
public class UserDetailPresenter : IPresenter<IUserDetailView>
{
private IDetailView _view;
public UserDetailPresenter(IDetailView detailView)
{
_view = detailView;
}
public void Show(IUser user)
{
InitializeView(user);
_view.Show();
}
}
public class UserListPresenter
{
//private members (_userRepo, _listView, _detailPresenter)
public UserListView(IUserRepository userRepo, IListView listView, IDetailPresenter detailPresenter)
{
//wire up private members..
_listView.EditCommandFired += this.ShowEditForm;
}
private void OnListViewEditCommandFired(object sender, EventArgs args)
{
_detailPresenter.LoadUser(_listView.SelectedUser);
_detailPresenter.Show(); //modal
}
}
public class UserListForm : Form, IUserListView
{
public event EventHandler EditCommandFired;
public IUser SelectedUser { get { return gridView.FocusedRowHandle as IUser; } }
public void LoadUsers(List<IUser> users)
{
gridView.DataSource = users;
}
// other UI stuff
}
My problem is: I can only show the edit form once. As soon as I try to open it for a second time my View (the form) is disposed (System.ObjectDisposedException).
How do I fix that? Do I have the wrong approach here? Do I either cancel the form's close and just hide it and trust the garbage collector to collect it once the DetailPresenter is disposed? Do I create (new() up) a new presenter each time the Edit event is fired? I would then have to introduce some kind of factory as I somehow lose dependency injection. I'd appreaciate if someone could point out how the best practice in this case would look like and what I may be doing wrong here..
I was doing Winforms MVP a while ago so not sure if I can help, but the case my be as follows.
In my approach, the view was owning presenter, pseudo code:
MyForm form = new MyForm(new PresenterX);
form.Show(); //or showdialog
In this case instance is still there after closing.
In your case since presenter owns the view, its possible that once presenter is not used, GC disposes presenter and contained view.
Or even if presenter is still in use, since view is private GC may collect it once closed.
Try to debug in Release mode and see what happens with closed form instance.
EDIT:
Other idea is:
Create instance of view first and then pass to presenter
So approach that may fail (I don' see full code so guessing)
UserDetailPresenter p = new UserDetailPresenter(new YourView());
Try
YourForm view = new YourForm(); //as global variable, view should be reusable anyway
Somewhere in code
UserDetailPresenter p = new UserDetailPresenter(view);
p.Show(userInstance);
You're using one instance of DetailPresenter to show details for different objects. So you'll have to initialize the view of the DetailPresenter each time you want to show it, in your current implementation. This could be one way of doing it, the ListPresenter can inject a new instance of DetailsView everytime it asks the DetailPresenter to show it.
public class UserDetailPresenter : IPresenter<IUserDetailView>
{
private IDetailView _view;
public UserDetailPresenter()
{
}
public void Show(IUser user, IDetailView detailView)
{
_view = detailView;
InitializeView(user);
_view.Show();
}
}
Or another cleaner way could be some sort of ViewFactory to get a new instance of the view before showing it.
private IDetailViewFactory _detailViewFactory;
public UserDetailPresenter(IDetailViewFactory detailViewFactory)
{
_detailViewFactory = detailViewFactory;
}
public void Show(IUser user )
{
_view = _detailViewFactory.Resolve();//Some method to get a new view
InitializeView(user);
_view.Show();
}
But if you want to do it a bit differently, this is more passive view way.
In the ListPresenter:
private void OnListViewEditCommandFired(object sender, EventArgs args)
{
_listView.Show(_listView.SelectedUser);//tells view to show another view
}
In the ListView:
public ListView()
{
new ListPresenter(this); // initializes presenter
}
public void Show(IUser user)
{
new DetailsView(user); // creates a new view
}
In the DetailsView:
public DetailsView(IUser user)
{
new DetailsPresenter(this, user); //creates presenter
}
Finally:
public class UserDetailPresenter : IPresenter<IUserDetailView>
{
private IDetailView _view;
public UserDetailPresenter(IDetailView detailView, IUser user)
{
_view = detailView;
LoadUser(user);
_view.SomeProperty = _userData;//to populate view with data
_view.Show(); // tells the view to show data
}
}
Related
I'm using mvvmcross and xamarin to bind an ObservableCollection to a UITableView. The collection is updated in place using the Add, Remove and Move methods. These calls correctly trigger INotifyCollectionChanged events and the TableView is updated as expected the first time the view containing the table is shown. If the user navigates away from the original view as part of the normal application flow but later returns the correct data is loaded into the table but calls to add, move and remove no longer update the table.
The INotifyCollectionChanged events are still being fired when the collection is updated
If I manually subscribe to these events in my subclass of MvxStandardTableViewSource and try and call ReloadData on the UITableView still does not update
My presenter is creating a new instance of the viewmodel and view each time the page is visited.
I'm also using Xamarin-Sidebar (https://components.xamarin.com/view/sidebarnavigation) for navigation in my application with a custom presenter to load the views but as far as I can tell the view is initialised via exactly the same code path whether it's the first or subsequent visit.
My presenters Show() method looks like this:
public override void Show(MvxViewModelRequest request)
{
if (request.PresentationValues != null)
{
if(NavigationFactory.CheckNavigationMode(request.PresentationValues, NavigationFactory.ClearStack))
{
MasterNavigationController.ViewControllers = new UIViewController[0];
base.Show(request);
}
else if(NavigationFactory.CheckNavigationMode(request.PresentationValues, NavigationFactory.LoadView))
{
var root = MasterNavigationController.TopViewController as RootViewController;
var view = this.CreateViewControllerFor(request) as UIViewController;
root.SidebarController.ChangeContentView(view);
}
}
else
{
base.Show(request);
}
}
The binding in my ViewController looks like this:
public override void ViewDidLoad()
{
base.ViewDidLoad();
View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
var source = new TracksTableSource(TableView, "TitleText Title; ImageUrl ImageUrl", ViewModel);
TableView.Source = source;
var set = this.CreateBindingSet<TracksViewController, TracksViewModel>();
set.Bind(source).To(vm => vm.PlaylistTable);
set.Apply();
}
And my viewmodel is as below where PlaylistTable is a subclass of ObservableCollection with the Update method using add, move and remove to keep the collection up to date.
public class TracksViewModel : MvxViewModel
{
private readonly IPlaylistService _playlistService;
private readonly IMessengerService _messengerService;
private readonly MvxSubscriptionToken _playlistToken;
public PlaylistTable PlaylistTable { get; set; }
public TracksViewModel(IPlaylistService playlistService, IMessengerService messengerService)
{
_playlistService = playlistService;
_messengerService = messengerService;
if (!messengerService.IsSubscribed<PlaylistUpdateMessage>(GetType().Name))
_playlistToken = _messengerService.Subscribe<PlaylistUpdateMessage>(OnDirtyPlaylist, GetType().Name);
}
public void Init(NavigationParameters parameters)
{
PlaylistTable = new PlaylistTable(parameters.PlaylistId);
UpdatePlaylist(parameters.PlaylistId);
}
public async void UpdatePlaylist(Guid playlistId)
{
var response = await _playlistService.Get(playlistId);
PlaylistTable.Update(new Playlist(response));
}
private void OnDirtyPlaylist(PlaylistUpdateMessage message)
{
UpdatePlaylist(message.PlaylistId);
}
}
This setup works perfectly the first time the view is initialised and updates the table correctly, it's only the second and subsequent times the view is initialised that the table fails to update. Can anyone explain why the binding fails when it appears the view is created using the same techniques in both instances?
I can post additional code if required but I believe the issue will be how I'm using the presenter since the code I've not posted from PlaylistTable functions correctly in unit tests and on first viewing.
I'm new on Caliburn Micro and want some advice on which path to take to devolop my app interface and navigation between views.
My idea is to have a MainWindow which will contain a menu of buttons, each one related with a specific view. Each view will be stored in a separated WPF UserControl. The mainWindow will also contain a TabControl bound to an ObservableCollection of tabs on viewmodel. Everytime a button on menu is clicked, I want to add a new tab with a ContentPresenter inside that will dynamically load a view and its corresponding viewmodel.
So my questions:
1) Should I use a Screen Collection here?
2) Should the UserControl implement Screen interface?
3) How do I tell MainWindow ViewModel which view to load on the new added tab maintaining viewmodels decoupled?
Thanks to everyone in advance.
UPDATE
After a lot of reading and some help of the community I managed to resolve this. This is the resultant AppViewModel:
class AppViewModel : Conductor<IScreen>.Collection.OneActive
{
public void OpenTab(Type TipoVista)
{
bool bFound = false;
Screen myScreen = (Screen)Activator.CreateInstance(TipoVista as Type);
myScreen.DisplayName = myScreen.ToString();
foreach(Screen miItem in Items)
{
if (miItem.ToString() == myScreen.ToString())
{
bFound = true;
ActivateItem(miItem);
}
}
if (!bFound) ActivateItem(myScreen);
}
public ObservableCollection<MenuItem> myMenu { get; set; }
public ObservableCollection<LinksItem> myDirectLinks { get; set; }
public ICommand OpenTabCommand
{
get
{
return new RelayCommand(param => this.OpenTab((Type) param), null);
}
}
public AppViewModel()
{
OpenTab(typeof(ClientsViewModel));
MenuModel menu = new MenuModel();
myMenu = menu.getMenu();
myDirectLinks = menu.getLinks();
}
public void CloseTab(Screen param)
{
DeactivateItem(param, true);
}
}
I have to keep the ICommand from OpenTabCommand because the name convention of Caliburn.micro doesn't seems to work inside DataTemplate. Hope it could help someone else. Thanks to all
I've done something very similar using Caliburn.Micro, and based it on the SimpleMDI example included with the examples, with a few tweaks to fit my needs.
Much like in the example, I had a main ShellViewModel:
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive
{
}
with a corresponding ShellView containing a TabControl - <TabControl x:Name="Items">, binding it to the Items property of the the Conductor.
In this particular case, I also had a ContextMenu on my ShellView, bound (using the Caliburn.Micro conventions), to a series of commands which instantiated and Activated various other ViewModels (usually with a corresponding UserControl, using the ActivateItem method on the Conductor.
public class YourViewModel: Conductor<IScreen>.Collection.OneActive
{
// ...
public void OpenItemBrowser()
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
In that case, I didn't require the ViewModels to be created with any particular dependency, or from any other locations in the program.
At other times, when I've needed to trigger ViewModel from elsewhere in the application, I've used the Caliburn.Micro EventAggregator to publish custom events (e.g. OpenNewBrowser), which can be handled by classes implementing the corresponding interface (e.g. IHandle<OpenNewBrowser>), so your main ViewModel could have a simple Handle method responsible for opening the required View:
public class YourViewModel: Conductor<IScreen>.Collection.OneActive, IHandle<OpenNewBrowser>
{
// ...
public void Handle(OpenNewBrowser myEvent)
{
// Create your new ViewModel instance here, or obtain existing instance.
// ActivateItem(instance)
}
}
This section of the documentation will probably be useful, especially the Simple MDI section.
Additional code I mentioned in the comments:
I sometimes use a generic method along these lines ensure that if I have an existing instance of a screen of a particular type, switch to it, or create a new instance if not.
public void ActivateOrOpen<T>() where T : Screen
{
var currentItem = this.Items.FirstOrDefault(x => x.GetType() == typeof(T));
if (currentItem != null)
{
ActivateItem(currentItem);
}
else
{
ActivateItem(Activator.CreateInstance<T>());
}
}
Used like:
public void OpenBrowser()
{
this.ActivateOrOpen<BrowserViewModel>();
}
When I log into my application, I pass the cUserEntity class which holds all the details of the logged in user, including UserID and Username, to the Dashboard form. From here, I can continue to pass the details around from class to class and form to form. For example (Ignore the generic bit in this example):
Login:
xamlDashboard Manager = new xamlDashboard(_loggedInUser);
Generic Gen = new Generic(_loggedInUser);
Manager.Show();
Dashboard:
cUserEntity _loggedInUser;
public xamlDashboard(cUserEntity loggedInUser)
{
InitializeComponent();
_loggedInUser = loggedInUser;
}
However, I have a Generic.Xaml page which creates a button at the top of every window. Behind the Generic.xaml is a Generic class, which holds the click_event for the created button. The click_event opens a new window to another form.
Now, I need that other form to have the logged in user details, and to do that, I assume I need to pass to the Generic.Xaml and then pass from there to the new form via the click_event. However, as I've read up and noticed, it doesn't seem to be possible as you can't pass a type to a Generic during runtime.
What I hoped to achieved (which failed):
public partial class Generic
{
cUserEntity _loggedInUser;
public Generic(cUserEntity loggedInUser)
{
_loggedInUser = loggedInUser;
}
private void btnHelp_Click(object sender, RoutedEventArgs e)
{
xamlHelp help = new xamlHelp(_loggedInUser);
help.Show();
}
}
Therefore, what is the best and most efficient method to be able to do this, and would appreciate examples, if possible.
It would be alot simpler to create a singleton object to store your logged in user...
public class UserAccount
{
private static User _currentUser;
private UserAccount() {}
public static User CurrentUser
{
set
{
_currentUser = value;
}
get
{
return _currentUser;
}
}
}
Then after login you would do this...
// Set the current User
UserAccount.CurrentUser = user;
Then in any class you need the currently logged in user... you could do...
var user = UserAccount.CurrentUser;
Obviously you would need to implement your own business rules around this but the concept is what I am trying to get across here, a static single instance of the user that can be accessed from anywhere.
I'm trying to use MVP and I notice that my View must know Model that should not happen in MVP I presume.
here is example:
public partial class TestForm : Form, ITestView
{
public void LoadList(IEnumerable<AppSignature> data)
{
testPresenterBindingSource.DataSource = data;
}
}
public interface ITestView
{
event EventHandler<EventArgs> Load;
void LoadList(IEnumerable<AppSignature> data);
}
public class TestPresenter
{
private ITestView view;
public TestPresenter(ITestView view)
{
this.view = view;
view.Load += View_Load;
}
private void View_Load(object sender, EventArgs e)
{
var data = // get from model
view.LoadList(data);
}
}
and the problem is that in TestForm I need reference to AppSignature.
In all tutorials I saw, there are some simple examples like
public void LoadList(IEnumerable<String> data) where there is no need reference to model. But how i.e DataGridView can publish current row data?
Your form is a View, it is not a Presenter. Thus it should implement interface ITestView:
public interface ITestView
{
event EventHandler Load;
void LoadList(IEnumerable<AppSignatureDto> data);
}
And your Presenter is someone, who subscribes to view's events and uses view properties to read and update view:
public class TestPresenter
{
private ITestView view;
public TestPresenter(ITestView view)
{
this.view = view;
view.Load += View_Load;
}
private void View_Load(object sender, EventArgs e)
{
List<AppSignature> signatures = // get from model
List<AppSignatureDto> signatureDtos = // map domain class to dto
view.LoadList(signatureDtos);
}
}
And you form, as I already said, is a view, it does not know anything about presenter and model:
public partial class TestForm : Form, ITestView
{
public event EventHandler Load;
private void ButtonLoad_Click(object sender, EventArgs e)
{
if (Load != null)
Load(this, EventArgs.Empty);
}
public void LoadList(IEnumerable<AppSignatureDto> data)
{
// populate grid view here
}
}
How to deal with reference to domain classes? Usually I provide to view only simple data (strings, integers, dates, etc), or I create data transfer objects, which are passed to view (you can name them FooView, FooDto, etc). You can easily map them with something like AtoMapper:
List<AppSignatureDto> signatureDtos =
Mapper.Map<List<AppSignature>, List<AppSignatureDto>>(signatures);
The View may have knowledge of Model as long as the interaction is limited to data binding only. i.e. View should not try to manipulate Model directly. View will always redirect user input to Presenter and Presenter will take care of further actions. If any action performed by Presenter results in a change in state of Model, Model will notify View via data binding. Model will be completely unaware of existence of View.
Is it OK to get DataSource in Presenter and there set its DataSource ?
e.g.
Presenter code:
Public void LoadData()
{
_view.Data.DataSource = Business.GetData().ToList();
}
Form code:
Public BindingSource Data
{
get
{
return this.bsData;
}
}
Thanks to that I dont need to add any references to the View, but I didn't see that solution in any other sources.
I am trying to validate User Input in Windows Forms Application (using MVP design Pattern). Since this is my first project using MVP, I am not very clear where and how to put the user input validation code.
To be specific, I have a Products form which contains two text box controls, Namely ProductName and ProductPrice.
Below is the code for my ProductForm, IProductView and ProductPresenter
IProductView.cs
public interface IProductView
{
string ProductName { get; set; }
int ProductPrice { get; set; }
event EventHandler<EventArgs> Save;
}
frmProduct.cs
public partial class frmProduct : Form,IProductView
{
ProductPresenter pPresenter;
public frmProduct()
{
InitializeComponent();
pPresenter = new ProductPresenter(this);
}
public new string ProductName
{
get
{
return txtName.Text;
}
}
public int ProductPrice
{
get
{
return Convert.ToInt32(txtPrice.Text);
}
}
public event EventHandler<EventArgs> Save;
}
ProductPresenter.cs
public class ProductPresenter
{
private IProductView pView;
public ProductPresenter(IProductView View)
{
this.pView = View;
this.Initialize();
}
private void Initialize()
{
this.pView.Save += new EventHandler<EventArgs>(pView_Save);
void pView_Save(object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
I do want to use the ErrorProvider(EP) Control + since I would be using EP control on many forms, I would really love if I could reuse most of the code by putting the EP code in some method and passing it the controls and appropriate message. Where should I put this validation code?
Regards,
I've used a base form with the error provider on and then had other forms inherit from this. I also put the visual error code in this base form also. This meant the same code is re-used. For Mvp, you could do something similar with a base form and an interface your application views inherit from. Your presenters would then see a uniform interface for setting validation states, messages, etc.