How do you implement a view with a grouped table in MonoTouch using MvvmCross, so you get something like this:
http://www.yetanotherchris.me/storage/downloads/UITableViewController.png
Right now I have this piece of code, but I cannot change the UITableViewStyle to Grouped:
public partial class HomeView : MvxBindingTouchTableViewController<HomeViewModel>
{
public HomeView(MvxShowViewModelRequest request)
: base(request)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
NavigationItem.SetRightBarButtonItem(new UIBarButtonItem("History", UIBarButtonItemStyle.Bordered, (sender, e) => ViewModel.DoGoToHistory()), false);
var source = new MvxActionBasedBindableTableViewSource(
TableView,
UITableViewCellStyle.Value1,
new NSString("HomeView"),
"{'TitleText':{'Path':'Date'},'DetailText':{'Path':'Location'},'SelectedCommand':{'Path':'ViewDetailCommand'}}",
UITableViewCellAccessory.DisclosureIndicator);
this.AddBindings(
new Dictionary<object, string>()
{
{ source, "{'ItemsSource':{'Path':'List'}}" },
{ this, "{'Title':{'Path':'TestTitle'}}"}
});
TableView.Source = source;
TableView.ReloadData();
}
}
Does anyone knows how to do this?
Your picture shows only one section.... assuming you are looking for just one section, but this grouped styling, then all you need to do is to introduce UITableViewStyle.Grouped somehow.
I'm not sure that the current MvxTableViewController exposes this for you - so you might either need to edit the Mvx source to add the appropriate constructors:
protected MvxTouchTableViewController(MvxShowViewModelRequest request, UITableViewStyle style = UITableViewStyle.Plain)
: base(style)
{
ShowRequest = request;
}
and
protected MvxBindingTouchTableViewController(MvxShowViewModelRequest request, UITableViewStyle style = UITableViewStyle.Plain)
: base(request, style)
{
}
Alternatively you could use a basic view controller (in which you add a Table as a subview) instead of a tableview derived view controller.
If you want multiple Groups, then you'll need to do a bit more work - as you'll need to work out how the bound TableViewSource works out the number of sections and the number of items in each section.
public class UserView : MvxTableViewController<UserViewModel>
{
public UserView()
:base(UITableViewStyle.Grouped)
{
}
}
Remember to make the constructor public and parameterless.
Related
I have question. I use to use Send to pass the viewmodel to show other page as shown below:
public class UsersViewModel : IUsersViewModel
{
void ShowCars()
{
MessagingCenter.Send<IUsersViewModel>(this, "ShowCarsViewPage");
}
}
As you see above, i use this. Now i have situation that i need to pass diffrent viewmoodel inside UsersViewModel. I want to add ShowBuildings inside UsersViewModel. The problem is as it's diffrent viewmodel to be passed i cannot use this which leds me to use new keyword and pass all dependencies. How can i overcome that?
void ShowBuildings()
{
MessagingCenter.Send<IBuildingsViewModel>(new Buildings(new DataStorage()), "ShowBuildingsViewPage");
}
My first thought is to pass that view model i need to use into UsersViewModel's ctor but not sure if this is right way like to insert another view model into other view model?:
public class UsersViewModel : IUsersViewModel
{
private readonly IBuildingsViewModel _buildingviewmodel;
UsersViewMode(IBuildingsViewModel buildingviewmodel)
{
_buildingviewmodel = buildingviewmodel;
}
//So then:
void ShowBuildings()
{
MessagingCenter.Send<IBuildingsViewModel>(_buildingviewmodel, "ShowBuildingsViewPage");
}
}
Check xamarin publisher documentation. The third parameter is the payload data that is being sent.
MessagingCenter.Send<MainPage, string>(this, "Hi", "John");
Tip: Try to bundle multiple ViewModels with an interface and pass that. In this way you will not be tied to specific ViewModels interfaces.
interface IBuildingCollection : IEnumerable<Building>
{
}
class ViewModel1 : IBuildingsViewModel, IBuildingCollection
{
}
class ViewModel2 : IBuildingsViewModel, IBuildingCollection
{
}
class UsersViewModel : IUsersViewModel
{
void ShowBuildings(IBuildingCollection collection)
{
MessagingCenter.Send<IUsersViewModel, IBuildingCollection>(this, "ShowBuildingsViewPage", collection);
}
}
class ReceiverViewModel : IReceiverViewModel
{
public ReceiverViewModel()
{
MessagingCenter.Subscribe<IUsersViewModel, IBuildingCollection>(this, "ShowBuildingsViewPage", myDelegate);
}
public void myDelegate(IBuildingCollection buildings)
{
// Do something with buildings
}
}
I made a BaseView-class that all my views inherit from. This contains the property Language. The language gets changed correctly in all "normal" views.
public abstract class BaseView<TModel> : WebViewPage<TModel>
{
string _language = TextRepository.DEFAULT_LANGUAGE;
public string Language { get { return _language; } }
public BaseView()
: base()
{
}
protected override void InitializePage()
{
base.InitializePage();
_language = Url.RequestContext.RouteData.Values["lang"] != null ? (string)Url.RequestContext.RouteData.Values["lang"] : _language;
}
}
public abstract class BaseView : BaseView<dynamic>
{
}
In one of my views, I call CandidatePartialRow with ajax, to add another partial row:
$("#addAnother").click(function () {
$.get('/MyController/CandidatePartialRow', function (template) {
$("#candidateEditor").append(template);
let target = $('#candidateEditor').children().last();
$('html, body').animate({
scrollTop: target.find('span').first().offset().top - 15
}, 1000);
});
});
public ActionResult CandidatePartialRow()
{
var view = PartialView("EditorTemplates/MyPartialView");
return view;
}
calling and appending works perfectly.
So I'm changing the language from "DE" (which is also default) to "FR". In the "normal" view, the language is changed. When I click the "add"-button for adding the partial view, the partial view is added, but with the default language ("DE").
Any idea what's the problem here? Or how I can change the language? (I also got the language in the controller - but this is also the default-language, otherwise I could set it in the CandidatePartialRow-method. But I don't know how..)
Okay simple solution...
The language wasn't added to the url, and so the default-language was always set with the routing.
Instead of '/MyController/CandidatePartialRow' I should have used
$.get('#Url.Action("CandidatePartialRow", "MyController")', function (template) {...})
I have a MasterDetail Application, and a NavButton to add a new item. Im am able to successfully pop the view back, however I need to reload the table nice that is done. So how exactly can my MasterViewController know when it was popped so it could reload?
Heres the Pop code:
NavigationController.PopViewController(true);
It functions as expected, I just then need MasterViewController to reload the table, which I made a method for:
public void ReloadTable()
{
TableView.ReloadData ();
}
Try this:
public partial class MasterViewController : UIViewController
{
public void ReloadTable()
{
TableView.ReloadData ();
}
}
public partial class DetailViewController : UIViewController
{
public override void ViewWillDisappear (bool animated)
{
var masterViewController = NavigationController.ViewControllers.OfType<MasterViewController> ().FirstOrDefault();
if (masterViewController != null) {
masterViewController.ReloadTable ();
}
base.ViewWillDisappear (animated);
}
}
You need to add using System.Linq; to get the OfType method.
Objective-C
It can be done by checking if navigationController contains viewcontroller like this:
Note viewControllerA pushes viewControllerB
Now in viewControllerB use viewWillDisappear method
- (void)viewWillDisappear:(BOOL)animated
{
if (![[self.navigationController viewControllers] containsObject: self]) //any other hierarchy compare if it contains self or not
{
// the view has been removed from the navigation stack or hierarchy, back is probably the cause
// this will be slow with a large stack however.
viewControllerA *objViewControllerA = [[self.navigationController viewControllers] lastObject];
//NSLog(#"%#",objViewControllerA);
if (objViewControllerA)
{
[objViewControllerA viewPoppedBack];
}
}
}
Now add one method in viewControllerA
-(void)viewPoppedBack
{
//reload tableview here
[yourTableViewHere reloadData];
}
Does anyone know how to view an existing IMvxViewModel?
In my app, I have already created a bunch of ViewModels (PhotoViewModel) inside of another view model. They exist as a property on the parent ViewModel (AlbumViewModel). It would be very nice to just show a particular instance of a PhotoViewModel instead of creating a new instance of that view model when I want to view it.
public class AlbumViewModel : MvxViewModel {
public ObservableCollection<PhotoViewModel> Photos
{
get { return GetValue(() => Photos); }
set { SetValue(value, () => Photos); }
}
}
public class PhotoViewModel : MvxViewModel { }
I was wondering if there was a way, other then creating my own IMvxViewModelLocator, to accomplish this task. I think having a protected method on the MvxNavigationObject called View could be really helpful both for new developers using the framework as well as performance. We'd be able to skip all of the reflection done currently to instantiate a view model.
The default ShowViewModel mechanism in MvvmCross uses page-based navigation - this navigation has to use Uris on WindowsPhone and Intents on Android.
Because of this, MvvmCross does not allow navigation by 'rich' objects - simple serialisable POCOs are Ok, but complicated 'rich' objects are not supported.
This is further essential because of 'tombstoning' - if your app/page/activity is later rehydrated then you cannot be sure of what historic View or ViewModel objects are actually in your history "back" stack.
If you want to navigate by rich object then the best way is to store those rich objects in a lookup service and to then navigate by some key/index into the lookup. However, I would personally call those lookedup objects Models rather than ViewModels (but the boundary does sometimes become blurred!)
Although based on MvvmCross v1 code, this question still gives quite a good background to this - What is the best way to pass objects to "navigated to" viewmodel in MVVMCross?
Some more up-to-date explanations include:
How to pass data across screens using mvvmcross
Custom types in Navigation parameters in v3
https://github.com/slodge/MvvmCross/wiki/ViewModel--to-ViewModel-navigation (under construction)
One final thing....
... the MvvmCross manifesto insists that MvvmCross is very open to customisation ...
Because of this you can override MvvmCross navigation and view model location if you want to. To do this, creating your own IMvxViewModelLocator would probably be a good way to start.
After some testing, below is a proposed solution. I'm not 100% in love with it, but it does work and provide the type developer experience I was looking for. So lets dig in.
To start, all of my ViewModels (VM) inherit from a base VM, AVM. This abstract base class supports looking up of an object as a public static method. It's a little gross, but it works well if you're willing to sip on the Kool-Aid. Below is the portion of the class that's relevant to this problem:
public abstract class AVM : MvxViewModel {
private static readonly Dictionary<Guid, WeakReference> ViewModelCache = new Dictionary<Guid, WeakReference>();
private static readonly string BUNDLE_PARAM_ID = #"AVM_ID";
private Guid AVM_ID = Guid.NewGuid();
private Type MyType;
protected AVM()
{
MyType = this.GetType();
ViewModelCache.Add(AVM_ID, new WeakReference(this));
}
public static bool TryLoadFromBundle(IMvxBundle bundle, out IMvxViewModel viewModel)
{
if (null != bundle && bundle.Data.ContainsKey(BUNDLE_PARAM_ID))
{
var id = Guid.Parse(bundle.Data[BUNDLE_PARAM_ID]);
viewModel = TryLoadFromCache(id);
return true;
}
viewModel = null;
return false;
}
private static IMvxViewModel TryLoadFromCache(Guid Id)
{
if (ViewModelCache.ContainsKey(Id))
{
try
{
var reference = ViewModelCache[Id];
if (reference.IsAlive)
return (IMvxViewModel)reference.Target;
}
catch (Exception exp) { Mvx.Trace(exp.Message); }
}
return null;
}
protected void View()
{
var param = new Dictionary<string, string>();
param.Add(BUNDLE_PARAM_ID, AVM_ID.ToString());
ShowViewModel(MyType, param);
}
In order to get this all wired up, you have to create a custom view model locator. Here's the custom locator:
public class AVMLocator : MvxDefaultViewModelLocator
{
public override bool TryLoad(Type viewModelType, IMvxBundle parameterValues, IMvxBundle savedState, out IMvxViewModel viewModel)
{
if (AVM.TryLoadFromBundle(parameterValues, out viewModel))
return true;
return base.TryLoad(viewModelType, parameterValues, savedState, out viewModel);
}
}
Lastly you have to wire up. To do so, go into your App.cs and override CreateDefaultViewModelLocator like so:
protected override IMvxViewModelLocator CreateDefaultViewModelLocator()
{
return new AVMLocator();
}
You're all set. Now in any of your derived ViewModels that are already alive and well, you can do the following:
myDerivedVM.View();
There's still some more I need to do (like making sure the WeakReferences do their job and I don't have memory leaks and some additional error handling), but at the very least it's the experience I was going for. The last thing I did was add the following command to the AVM base class:
public MvxCommand ViewCommand
{
get { return new MvxCommand(View); }
}
Now you can bind that command to any UI object and when invoked, it'll launch that view with that very instance of the VM.
Stuart, thanks for your help in steering me in the right direction. I'd be interested in hearing your feedback on the solution I provided. Thanks for all of your work with MVVMCross. It really is a very beautiful bit of code.
Cheers.
For the past couple of weeks I've been working on developing a cross platform app (IOS/Android/WP7) using the MVVMCross framework. Today I ran into a problem I don't really know how to solve, so hopefully you can push me in the right direction.
In the IOS I have the following construction for navigating to another page (the code below is located in a ViewModel):
KeyValuePair<string,string> kvpAct1 = new KeyValuePair<string, string>("short", ".countertest5");
public IMvxCommand BeckhoffActuator1
{
get
{
return new MvxRelayCommand<Type>((type) => this.RequestNavigate<Beckhoff.BeckhoffActuatorViewModel>(kvpAct1));
}
}
When this IMvxCommand is fired (button pressed) the next View is loaded, in this case the BeckhoffActuatorViewModel. In the code of the BeckhoffActuatorView I use the keyvaluepair from above:
public class BeckhoffActuatorView : MvxTouchDialogViewController<BeckhoffActuatorViewModel>
{
ICollection<string> icol;
public BeckhoffActuatorView(MvxShowViewModelRequest request) : base(request, UITableViewStyle.Grouped, null, true)
{
icol = request.ParameterValues.Values;
}
public override void ViewDidLoad()
{
//Code
}
}
This construction is working fine in IOS, but I would like to use the same construction in my android App.
The code in the ViewModel hasn't changed since that's the whole idea of MVVM. But the code of the BackhoffActuatorView is different for Android:
public class BeckhoffActuatorView : MvxBindingActivityView<BeckhoffSensorViewModel>
{
public ICollection<string> icol;
public BeckhoffActuatorView()
{
Debug.WriteLine("Standard");
}
public BeckhoffActuatorView(MvxShowViewModelRequest request)
{
Debug.WriteLine("Custom");
icol = request.ParameterValues.Values;
}
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.BeckhoffActuatorView);
}
}
The code above isn't working, the MvxBindingActivityView doesn't seem to implement something similar to the ViewController I use in IOS. The code only come in the standard constructor, and when I leave that one out completely it won't compile/run.
Does anyone know know I can access the keyvaluepair I send with the RequestNavigate? Thank you!
MVVMCross is very convention based - and it works on the idea of passing messages between ViewModels wherever possible.
If you navigate to a ViewModel using:
KeyValuePair<string,string> kvpAct1 = new KeyValuePair<string, string>("short", ".countertest5");
public IMvxCommand BeckhoffActuator1
{
get
{
return new MvxRelayCommand<Type>((type) => this.RequestNavigate<Beckhoff.BeckhoffActuatorViewModel>(kvpAct1));
}
}
then you should be able to pick that up in the BeckhoffActuatorViewModel using the constructor:
public class BeckhoffActuatorViewModel : MvxViewModel
{
public BeckhoffActuatorViewModel(string short)
{
ShortValue = short;
}
private string _shortValue;
public string ShortValue
{
get
{
return _shortValue;
}
set
{
_shortValue = value;
FirePropertyChanged("ShortValue");
}
}
}
And your views can then access ViewModel.ShortValue (for iOS this can be done after base.ViewDidLoad(), for Android after OnCreate() and for WP7 after OnNavigatedTo)
For an example of this, take a look at the TwitterSearch example:
https://github.com/slodge/MvvmCrossTwitterSearch
This has a HomeViewModel which calls navigate using:
private void DoSearch()
{
RequestNavigate<TwitterViewModel>(new { searchTerm = SearchText });
}
and a TwitterViewModel which receives the searchTerm using the constructor:
public TwitterViewModel(string searchTerm)
{
StartSearch(searchTerm);
}
Please note that only strings are allowed in this message passing at present - but you can always serialise your own objects using JSON.Net - or you can extend the framework - it's open source.
Please note that only strings, ints, doubles and bools are allowed in this constructor parameter passing at present - this is due to serialisation requirements for Xaml Urls and for Android Intents. If you want to experiment with navigation using your own custom serialised objects, then please see http://slodge.blogspot.co.uk/2013/01/navigating-between-viewmodels-by-more.html.
Also, note that if you want to use the anonymous object navigation (RequestNavigate<TwitterViewModel>(new { searchTerm = SearchText });) then you will need to make sure that an InternalsVisibleTo attribute is set - see https://github.com/slodge/MvvmCrossTwitterSearch/blob/master/TwitterSearch.Core/Properties/AssemblyInfo.cs:
[assembly: InternalsVisibleTo("Cirrious.MvvmCross")]
Further... not for the faint-hearted... and this isn't "good mvvm code"... but if you really want/need to access the MvxShowViewModelRequest data inside an Android activity, then you can extract it from the incoming Intent - there's an Extras string containing the request (see the deserialisation in CreateViewModelFromIntent in https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross/Android/Views/MvxAndroidViewsContainer.cs)