There are several tutorials and examples on this topic out there but they are all a sort of generic build only in one class to show how it works generally.
So my question is when I would like to follow the MVVM pattern where I have to implement all my tasks?
Given the following:
Model:
class Model {
/* When I place the Task here how can I deal with arguments and results from ViewController? */
public BufferedImage bigTask (String this, String and, Image that){
// Some code to build a BufferedImage
}
}
ViewModel:
class ViewController {
private BufferedImage myBufferedImage;
#FXML
private Button aButton;
/*Should I implement my Task here? But how I get information about progress? */
final Task<Integer> myTask = new Task<Integer>(){
#Override
protected Integer call() throws Exception{
updateProgress( // How to get here? Is it the right place? )
return null;
}
};
#FXML
void setOnAction(ActionEvent actionEvent){
myBufferedImage = Model.bigTask("this", "that", new Image("path"));
}
}
Hope I could explain the problem.
Thanks in advance!
In general your tasks should be implemented in the ViewModel.
The actual implementation of business logic should be done in the Model for example in a service class. The ViewModel can then use this service and handle all the ui specific actions like creating a Task for async execution and updating a progress value. However the ViewModel may not directly update a ProgressIndicator but instead the viewModel could have a DoubleProperty "progress" that is updated in the ViewModel. In the ViewController/CodeBehind you bind the actual ProgressIndicator to this progress property of the ViewModel. This way the ViewModel is independent from actual UI controls and the View doesn't contain any business logic.
Your example is a little bit special I think. Normally I would say that "BufferedImage" is a ui specific class that only belongs to the View and not the ViewModel nor the Model. However your example looks like BufferedImage is the result of a business action. In this case I would create a ObjectProperty<BufferedImage> in your ViewModel and put the task to load the image in the ViewModel too. In your ViewController I would add a listener to this property and put the image into the ui when it changes.
This way your View class is independet of how the image is loaded.
Related
I want to follow the MVVM pattern as much as possible, but I don't know if I am doing the navigation quite well. Note that I am using a MasterDetail page and I want to maintain the Master page, changing only the Detail side when I navigate.
Here is the way I navigate from my ViewModel. In this example, from ViewModelOne to ViewModelTwo:
public class ViewModelOne : ViewModelBase
{
private void GoToViewTwo()
{
var viewTwo = new ViewTwo(new ViewModelTwo());
((MasterView)Application.Current.MainPage).NavigateToPage(viewTwo);
}
}
MasterView implementation:
public class MasterView : MasterDetailPage
{
public void NavigateToPage(Page page)
{
Detail = new NavigationPage(page);
IsPresented = false;
}
}
ViewTwo implementation:
public partial class ViewTwo : PageBase
{
public MenuView(ViewModelTwo vm)
: base(vm)
{
InitializeComponent();
}
}
PageBase implementation:
public class PageBase : ContentPage
{
public PageBase(ViewModelBase vmb)
{
this.BindingContext = vmb;
}
}
Is this the best approach (and best performance) for do the navigation? When I do some navigations, the app starts to run slower and maybe there is something I am not doing fine.
Is this the best approach for do the navigation showing always a MasterDetail page?
Thanks.
I think you're certainly on the right track, however there are a few issues here:
Firstly, you should not be instantiating Views in your view model. As soon as your View Model becomes aware of the view, then you've pretty much broken the pattern.
var viewTwo = new ViewTwo(new ViewModelTwo());
Your view creation should be the responsibility of the master view. In fact, you don't even need to worry about creating views, as you can use a DataTemplate for that. I'll explain that later.
Firstly, we need to separate your View Models from the Views, here is what I propose:
You'll need some kind of base class or interface for your view models in order to keep things generic, you'll see why in a moment. Let's start out with a simple example:
public abstract class ViewModel : INotifyPropertyChanged
{
public event EventHandler OnClosed;
public event EventHandler OnOpened;
//Don't forget to implement INotifyPropertyChanged.
public bool IsDisplayed { get; private set; }
public void Open()
{
IsDisplayed = true;
//TODO: Raise the OnOpened event (Might be a better idea to put it in the IsDisplayed getter.
}
public void Close()
{
IsDisplayed = false;
//TODO: Raise the OnClosed event.
}
}
This of course is a very simple base view model, you can extend on this later, the main reason for this is to allow you to create a master view model which will be responsible for displaying your current page. Here's a simple example of a master view model:
public class MasterViewModel : INotifyPropertyChanged
{
//Don't forget to implement INotifyPropertyChanged.
public ViewModel CurrentPage { get; private set; }
public MasterViewModel()
{
//This is just an example of how to set the current page.
//You might want to use a command instead.
CurrentPage = new MovieViewModel();
}
//TODO: Some other master view model functionality, like exiting the application.
}
Please note that INotifyPropertyChanged would probably be better in some kind of base class, instead of having to re-implement the same code over and over.
Now the MasterViewModel is pretty simple, it just holds the current page, however the purpose of having the master is to allow for application level code to be executed, like closing the app, that way you're keeping this logic away from your other view models.
Right, now onto the good stuff.
Your detail has a relationship to it's parent, therefore it would make sense to say that it is the responsibility of the parent to manage it. In this case, your master-detail view model would look something like this:
public class MovieViewModel : ViewModel
{
protected PickGenreViewModel ChildViewModel { get; private set; }
public MovieViewModel()
{
ChildViewModel = new PickGenreViewModel();
//TODO: Perhaps subscribe to the closed event?
}
//Just an example but an important thing to note is that
//this method is protected because it's the MovieViewModel's
//responsibility to manage it's child view model.
protected void PickAGenre()
{
ChildViewModel.Open();
}
//TODO: Other view model functionality.
}
So, now we've got some kind of view model structure here, I bet you're asking "What about the views?", well, that's where the DataTemplate comes in.
In WPF, it's possible to assign a view to a Type, for example, you can assign the MovieView to the MovieViewModel in XAML, like this:
xmlns:Views="clr-namespace:YourNamespace.Views"
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels"
...
<DataTemplate DataType="{x:Type ViewModels:MovieViewModel}">
<Views:MovieView/>
</DataTemplate>
Ok great!, now to get the Master View to actually display the current page's view, you simply need to create a ContentPresenter, and bind it's Content to the CurrentPage. Your Master View will look something like this:
<Window
...
xmlns:ViewModels="clr-namespace:YourNamespace.ViewModels">
<Window.DataContext>
<ViewModels:MasterViewModel/>
</Window.DataContext>
<Grid>
<ContentPresenter Content="{Binding CurrentPage}"/>
</Grid>
To extend this further, it's not only the MasterView that needs to contain a ContentPresenter for it's child, it is also the MovieView which needs one for it's child PickGenreViewModel. You can use the same method again:
<Grid>
<!-- The main view code for the movie view -->
...
<Border Visibility="{Binding ChildViewModel.IsDisplayed, Converter=...">
<ContentPresenter Content="{Binding ChildViewModel}"/>
</Border>
</Grid>
Note: Use a boolean to Visibility converter to determine whether to display the child content.
Using this method you don't have to worry about instantiating any views, as the DataTemplate and ContentPresenter handles that for you, all you need to worry about is mapping the view models to the appropriate view.
Phew! That was a lot to take in.
The main points to take away from this are:
You shouldn't be creating views in your view models, remember, UI is UI, Data is Data.
View model responsibilities lie with whoever owns them, for a parent-child relationship, it makes sense to let the parent manage the child, as opposed to a view model manager.
A final note is that there are certainly more than one other ways of achieving this, as I just mentioned, some kind of view and view model manager to be responsible for creating/removing views and view models.
Scenario:
MainWindow has a Menu About which relates to AboutWindow.
About Meny is triggered by command:
<MenuItem Header="_About" Command="{Binding OpenAbout}"/>
OpenAbout is property like that:
private RelayCommand _openAbout;
public RelayCommand OpenAbout
{
get
{
return _openAbout ?? (_openAbout = new RelayCommand(() => Messenger.Default.Send(new NotificationMessage("ShowAboutView"))));
}
}
Notification message is registered in App.cs class as follows:
static App()
{
DispatcherHelper.Initialize();
}
public App()
{
RegisterMessenger();
}
public void RegisterMessenger()
{
Messenger.Default.Register<NotificationMessage>(this, ProcessShowAboutView);
}
private void ProcessShowAboutView(NotificationMessage message)
{
AboutWindow view = new AboutWindow();
view.Show();
}
I analysed another questions like that:
How to open a new window using MVVM Light Toolkit
WPF MVVM - How to Show a view from MainWindowViewModel upon Clicking on button
I like Messenger functionality but however I am not sure If above solution is a good one.
I would be thankful for any advise!
As depicted above, Registering messages is done in App Config.
I consider it not be a good place therefore I need to know what place would be better.
Another place to consider would be Locator
I personaly would register the messages in App.xaml.cs in the OnStartup method (WPF) and in the set up method of the unit test (dont forget to unregister everything in the tear down method).
I am using the excellent Mvvmcross and Ninja Coder for Mvvmcross for building a cross platform app. For my windows store app I have created a view and a view model using Ninja coder. I have also created a UserControl which will be referenced in the view. Hence I need to bind the same viewmodel to the User control also. I have been trying to set the Data context of the user control to the singleton instance of viewmodel. I have set the data context of the user control like below.
public sealed partial class SearchResultsGridViewControl : UserControl
{
private SearchresultsViewModel _viewModel;
public SearchResultsGridViewControl()
{
this.InitializeComponent();
_viewModel = Mvx.IocConstruct<SearchresultsViewModel>();
this.DataContext = _viewModel;
}
}
But when I refer this User Control in my main view, it throws an error in XAML saying "Object Reference not set to an instance of an object. Cannot create an instance of SearchResultsGridViewControl".
This is my viewmodel:
public class SearchresultsViewModel : BaseViewModel
{
private ISearchResultsService _searchResultsService;
public SearchresultsViewModel(ISearchResultsService searchResultsService)
{
_searchResultsService = searchResultsService;
var items = _searchResultsService.DisplaySearchResults();
SchoolDetails = new ObservableCollection<School>(items);
}
private ObservableCollection<School> _schoolDetails;
public ObservableCollection<School> SchoolDetails
{
get { return _schoolDetails; }
set
{
_schoolDetails = value;
RaisePropertyChanged(() => SchoolDetails);
}
}
public ICommand RefineCommand
{
get
{
refineCommand = refineCommand ?? new MvxCommand(FilterSearchResultsBasedOnRefine);
return refineCommand;
}
}
public void FilterSearchResultsBasedOnRefine()
{
SchoolDetails = new ObservableCollection<School>(_searchResultsService.FilterSchoolsBasedOnRefine(MidDayMeals, PlayGround, DigitalClassroom, DayBoarding, TransportationFacility));
}
}
The grid view in my usercontrol is getting populated when it loads for the first time. But when RefineCommand is called to update the collection from the main view, the grid view in usercontrol is not getting updated. And I am guessing its because of that error earlier in setting the data context of the user control to view model. Please let me know what could be going wrong. I have been banging my head about it for days.
I've been using MVVMCross with Windows Store fairly recently. Without looking back at my code, I'm pretty sure that the Datacontext will inherit from it's parent unless overridden.
So as long as the MvxPage that you have presented has a viewmodel, any user control that you add to it, either in XAML or in code-behind, should share the same data context. If you are looking at doing some MVVMCross data-binding from the User Control, you should probably make sure your User Control implements IMvxStoreView, and ensure that the ViewModel property is set to the value of DataContext.
Hope that help.
Cheers,
Tristan
I think your first problem "Object Reference not set to an instance of an object" is a design time only issue - because you are trying to set the viewmodel using Mvx. at design time. You can workaround this issue if you want to by using design time viewmodels and possibly also by using one of the design time helpers (see https://github.com/MvvmCross/MvvmCross/blob/v3.1/CrossCore/Cirrious.CrossCore.Wpf/Platform/MvxDesignTimeHelper.cs).
I've no idea what your second problem is "The grid view in my usercontrol is getting populated when it loads for the first time. But when RefineCommand is called to update the collection from the main view, the grid view in usercontrol is not getting updated" - this sounds like an issue either in your xaml or in the results returned from FilterSearchResultsBasedOnRefine. From the current level of detail, I can't see what it is. My "gut feeling" is that the problem won't be MvvmCross specific - it'll just be a general Mvvm/data-binding issue.
I am using MvvmCross for creation my Android-app and I faced with the following problem:
When I'm trying to show AlertDialog, that was created in ViewModel, the
"Unhandled Exception: Android.Views.WindowManagerBadTokenException" appears.
public class MyViewModel : MvxViewModel
{
public ICommand ShowAlertCommand { get; private set; }
public AuthorizationViewModel()
{
ShowAlertCommand = new MvxCommand(() =>
{
var adb = new AlertDialog.Builder(Application.Context);
adb.SetTitle("Title here");
adb.SetMessage("Message here");
adb.SetIcon(Resource.Drawable.Icon);
adb.SetPositiveButton("OK", (sender, args) => { /* some logic */});
adb.SetNegativeButton("Cancel", (sender, args) => { /* close alertDialog */});
adb.Create().Show();
});
}
}
When I was researching I have found that it happens because of transmission of the reference to the Context but not on the Activity in the AlertDialog.Builder.
In this topic I found the following decision:
Receive references to the current Activity through the use of GetService(), but I didn't found mvvmcross plugins for work with IMvxServiceConsumer, IMvxAndroidCurrentTopActivity interfaces.
My question is can I show AlertDialog from ViewModel? And how can I get the reference to Activity, but not to the Application.Context?
And what is the correct way to close AlertDialog that the user would stay on the current View?
In general, you should try not to put this type of code into ViewModels
because ViewModels should stay platform independent
because ViewModels should be unit testable - and it's hard to unit test when the code shows a dialog
I'd also recommend you don't put code like this inside a ViewModel Constructor - these constructors are generally called during navigations and displaying a Dialog during a transition is likely to be problematic.
With those things said, if you do want to get hold of the current top Activity within any code, then you can do this using the IMvxAndroidCurrentTopActivity
public interface IMvxAndroidCurrentTopActivity
{
Activity Activity { get; }
}
Using this, any code can get the current Activity using:
var top = Mvx.Resolve<IMvxAndroidCurrentTopActivity>();
var act = top.Activity;
if (act == null)
{
// this can happen during transitions
// - you need to be sure that this won't happen for your code
throw new MvxException("Cannot get current top activity");
}
var dlg = new AlertDialog.Builder(act);
//...
dlg.Create().Show();
The use of IMvxAndroidCurrentTopActivity is discussed in MvvmCross: How to pass Android context down to MvxCommand?
The approach taken in that question/answer is also one of the ways I would generally approach showing dialogs from a ViewModel:
I would create an IFooDialog interface
Ideally I would probably make this interface asynchronous - e.g. using async or using an Action<DialogResult> callback parameter
on each platform I would implement that in the UI project
the ViewModels can then use IFooDialog when a dialog is needed and each platform can respond with an appropriate UI action
This 'Dialog Service' type of approach is common in Mvvm - e.g. see articles like http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern (although that article is very Windows specific!)
There are also a few other questions on here about MvvmCross and dialogs - although they may contain reference to older v1 or vNext code - e.g. Alerts or Popups in MvvmCross and Unable run ProgressDialog - BadTokenException while showind
I have a problem which I tried to explained in the Image.I hope that will help all to understand what I need.
My Base Page is like this (menuNavPanel is the tree panel):
<div class="colContainer">
<div class="leftColumn" >
<div wicket:id="menuNavPanel"></div>
</div>
<div class="rightColumn">
<wicket:child/>
</div>
</div>
And Ny BIA Page which is a child of Base Page is like this:
<wicket:extend>
<div wicket:id="bodyPanel"></div>
</wicket:extend>
in my Tree Panel, when I click on a node the code is this:
#Override
protected void onNodeLinkClicked(AjaxRequestTarget target, TreeNode node) {
super.onNodeLinkClicked(target, node);
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
Unit unitObject =(Unit) treeNode.getUserObject();
// I want to call bodyPanel fo child page passing the unitObject param
}
Now, How can I call bodyPanel fo child page passing the unitObject param from the tree panel of the parent page?
Am I been able to express my problem? Hoping to get some help :)
Instead of doing the override method, upgrade to Wicket 1.5 and utilize the new event bus to communicate between your components. You can create a custom, type-safe, event that is specific to your component's use case: for example "ItemAddedToShoppingCart" or "GlobalThermoNuclearWarStarted".
The linked article in the 1.5 migration guide provides enough information on how to set up things.
I'm not sure I understand que question correctly. Your BasePage defines a left column with the TreePanel and lets subclasses expand themselves inside the right column div. You usually put a BodyPanel inside BasePages's subclasses. And now you want to invoke a BodyPanel's method on some event on the TreePanel.
You could do it with an overridable method on BasePage, which would be called in TreePanel through getPage(). Your child pages would override that method, and its implementation would call the BodyPanel they're holding.
public class BasePage ... {
// Hook
public void treePanelSelected(Object someObject) { }
...
}
public class ChildPage extends BasePage ... {
BodyPanel bodyPanel;
#Override
public void treePanelSelected(Object someObject) {
bodyPanel.selectionChanged/(someObject);
}
...
}
public class TreePanel ... {
...
#Override
protected void onNodeLinkClicked(AjaxRequestTarget target, TreeNode node) {
super.onNodeLinkClicked(target, node);
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)node;
Unit unitObject =(Unit) treeNode.getUserObject();
((BasePage)getPage()).treePanelSelected(unitObject);
}
}
From my ignorance on your specific needs and details of implementation, I don't see why is subclassing the BasePage necessary. You could add the BodyPanel right there in the BasePage and control it from the same class.
Thanks all, After reviewing all the nice options I finally opted out for the event bus way defined by martijn. What I did is I have created an event payload and connected the panels for the talking. I also needed to pass the selected Id / entity to the receiving panel.
Is there a way to set a compound property model of the receiving panel according to the model of the tree element so that I don't need to do the model manually ?
I did like this for the time being:
public class TreeNodeClickUpdate {
private final AjaxRequestTarget target;
private final long selectedId;
/**
* Constructor
*
* #param target
* #param selectedId
*/
public TreeNodeClickUpdate(AjaxRequestTarget target, long selectedId)
{
this.target = target;
this.selectedId = selectedId;
}
/** #return ajax request target */
public AjaxRequestTarget getTarget()
{
return target;
}
public long getSelectedId() {
return selectedId;
}
}
On the sender side I've done like this:
send(getPage(), Broadcast.BREADTH,
new TreeNodeClickUpdate(target, unitObject.getId()));
And on the receiving end I got it like this:
#Override
public void onEvent(IEvent<?> event) {
super.onEvent(event);
if (event.getPayload() instanceof TreeNodeClickUpdate)
{
TreeNodeClickUpdate update = (TreeNodeClickUpdate)event.getPayload();
setSelectedId(update.getSelectedId()); //sets to id field of panel
update.getTarget().add(this);
}
}
and for just as an example in my receiving panel, to get the value I have created a label like this:
label = new Label("label",new PropertyModel<BiaHomePanel>(this,"selectedId"));
Later, in reality I want to get information from the entity and show in form. Is there a nice way to pass models in a better way or I should pass as a parameter in event payload.
There are two ways to do this. One is cleaner, but requires more code. The other is quick and dirty.
Method 1: (Good)
Since your parent page is being extended, you can provide an abstract method in the parent like
protected abstract WebMarkupContainer getBodyPanel();
that is implemented in your child page and returns the appropriate panel. Then, you can call that method from the panel in your parent page. This is similar to the overrideable method suggested by the other user.
Method 2: (Bad)
The Wicket Component Hierarchy is shared between the parent and child pages. So, if you make sure that your bodyPanel has a unique wicketId and is added directly to the root of the page, you can probably just call
get("bodyPanelId");
and it will return the proper panel.
When I was facing the problem, I thought of two ways to solve this (pre 1.5):
a) implement a variation of the observer pattern to notify other component of events like outlined here: Realising complex cross-component ajax actions in wicket - The observer way
b) using wicket visitors to traverse the component tree doing the same.
I decided to go for variant a) but this introduces coupling from your component to your page-implementation which leads to problems when testing panels on their own. So maybe b) might be the better idea but since my application is running quite smoothly with a) implemented and the next big step will be switching over to 1.5 and the event bus, I haven't yet tried b).