Bind property from View to ViewModel - mvvmcross

I have a property MyFunc in my custom view:
public class MyView : MvxLinearLayout
{
public Func<Task<byte[]>> MyFunc { get; set; }
}
I would like to call this function from the ViewModel. How can I bind to it so I can have it in my ViewModel?
public class MyViewModel : MvxViewModel
{
public Func<Task<byte[]>> MyFunc { get; set; } // Bind to it here so I can call it within this class
}

You can't bindings work the other way around.
Source is always ViewModel
Target is always View
I don't know what you want to achieve with this. However, it looks like you want to populate your LinearLayout with images or some kind of byte data.
For that, use a Command. If you really want a platform specific function to trigger in your shared code, you can use MvxInteraction.

Related

Trouble creating a base ViewModel for MvvmCross 5.1.0

I'm currently diving into the world of Xamarain with the MvvmCross framework. In my current project I want to make use of a MVVM base ViewModel to be able to reuse some of my code in other ViewModels.
When trying to implement this I've ran into a problem when using the MvxViewModel which supports passing parameters between navigation.
public abstract class BaseViewModel<TParameter> : MvxViewModel, IMvxViewModel<TParameter> where TParameter : class
{
protected readonly IMvxNavigationService _navigationService;
public BaseViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
public new abstract Task Initialize(TParameter parameter);
}
This way I'm able to use the BaseViewModel as following.
public class ExampleViewModel : BaseViewModel<ExampleParameters>
{
private ExampleParameters _parameter;
public ExampleViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
}
public override Task Initialize(ExampleParameters parameter)
{
return Task.Run(() => { _parameter = parameter; });
}
}
In this situation I think this is a pretty good solution. The ExampleViewModel even tells me I need to implement the Initialize Task when I've forgotten.
Still this solution is not great in every situation. When I have ViewModel that doesn't require the passing of parameters I still need to specify a parameters object and implement the Initialize method.
public class ParameterlessViewModel : BaseViewModel<object>
{
public ParameterlessViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
}
public override Task Initialize(object parameter)
{
return Task.Run(() => { });
}
}
When removing the abstract method from the BaseViewModel I wont need to implement the Initialize method but then I won't be forced to implement it when I'm creating a ViewModel that requires the passing of parameters.
The above solution is workable but I'm curious if anyone ran into this same problem and maybe has a better solution? One which is good in both situations without having to setup two BaseViewModel classes.
Kind regards,
Jop Middelkamp
The documentation for this states: https://www.mvvmcross.com/documentation/fundamentals/navigation
If you have a BaseViewModel you might not be able to inherit MvxViewModel<TParameter> or MvxViewModel<TParameter, TResult> because you already have the BaseViewModel as base class. In this case you can implement the following interface:
IMvxViewModel<TParameter>, IMvxViewModelResult<TResult> or IMvxViewModel<TParameter, TResult>
In case you use TResult you can just copy the source code into your viewmodel:
public override TaskCompletionSource<object> CloseCompletionSource { get; set; }
public override void ViewDestroy()
{
if (CloseCompletionSource != null && !CloseCompletionSource.Task.IsCompleted && !CloseCompletionSource.Task.IsFaulted)
CloseCompletionSource?.TrySetCanceled();
base.ViewDestroy();
}
Do we do the add the Interface IMvxViewModel in the base class or the device class, can you give a simple example
In this case you can implement the following interface:
IMvxViewModel<TParameter>, IMvxViewModelResult<TResult> or IMvxViewModel<TParameter, TResult>

Caliburn Micro Screen Derived View Model Support Sub View Models?

Using Caliburn Micro v2 is there a way of composing view models inside a screen such that the child view models get the OnInitialize, OnActivate() and OnDeactivate(bool) calls?
I have something like the following view model WidgetsViewModel used to display a screen.
public class WidgetsViewModel : Screen, IHandle<WidgetUpdateEvent>
{
public ObservableCollection<WidgetSummaryViewModel> Widgets { get; set; }
...
public void Handle(WidgetUpdateEvent theEvent)
{
// Update the specific widget in Widgets collection
}
}
public class WidgetSummaryViewModel
{
public int Counter { get; set; }
}
I would like to move the handling of the WidgetUpdateEvent into the WidgetSummaryViewModel where it would sit more comfortably. Something like this:
public class WidgetSummaryViewModel : IHandle<WidgetUpdateEvent>
{
public int Counter { get; set; }
public WidgetSummaryViewModel(IEventAggregator theEventAggregator)
{
theEventAggregator.Subscribe(this);
}
public void Handle(WidgetUpdateEvent theEvent)
{
// Update this view model...
}
}
What makes me uncomfortable is calling Subscribe inside the view model constructor. It would be much better if the screen was able to call the OnInitialize, OnActivate and OnDeactivate for me as it does for Screen derived view models. Is there some way to compose sub view models inside a screen derived based view model?
Make WidgetsViewModel a conductor:
public class WidgetsViewModel : Conductor<WidgetSummaryViewModel>.Collection.AllActive
{
}
Update your data binding in WidgetsView:
{Binding Items} instead of {Binding Widgets}
Derive WidgetSummaryViewModel from Screen and override all the methods you need.
For further information regarding Conductors, visit Screens, Conductors and Composition.

mvvmcross - multiple Init methods in viewmodel with different signature not working

in a main viewmodel where i collect data from another viewmodels, I created in summary two or three public Init methods with different signatures. When i navigate back to the base viewmodel from the other viewmodels with ShowViewModel, I awaited that the right Init method will be executed, but this don't happen. Regarding the greet practical documentation here:
http://slodge.blogspot.ch/2013/03/v3-new-viewmodel-lifecycle.html
This should be work :-/.
I will explain this with some code.
My main view model is e.g.:
public class MainViewModel : MvxViewModel
{
MainViewModel() {}
public class ParameterFirst
{
public string Id { get; set; }
}
public class ParameterSecond
{
public string Id { get; set; }
}
public class ParameterSecond
{
public string Id { get; set; }
}
public class ParameterThird
{
public string Id { get; set; }
}
public void Init(ParameterFirst objFirst)
{
//do something
}
public void Init(ParameterSecond objSecond)
{
//do something
}
public void Init(ParameterThird objThird)
{
//do something
}
}
Then I will navigate from another viewmodel and await that the right Init method will be executed:
public class CollectData_ONE_ViewModel : MvxViewModel
{
CollectData_ONE_ViewModel() {}
public void DidWork()
{
//Hopefully the Init method with argument ParameterFirst should be called
base.ShowViewModel<MainViewModel>(new MainViewModel.ParameterFirst { Id = "11" });
}
}
next here the second viewmodel
public class CollectData_SECOND_ViewModel : MvxViewModel
{
CollectData_SECOND_ViewModel() {}
public void DidWork()
{
//Hopefully the Init method with argument ParameterFirst should be called
base.ShowViewModel<MainViewModel>(new MainViewModel.ParameterSecond { Id = "22" });
}
}
and the third viewmodel
public class CollectData_THIRD_ViewModel : MvxViewModel
{
CollectData_THIRD_ViewModel() {}
public void DidWork()
{
//Hopefully the Init method with argument ParameterFirst should be called
base.ShowViewModel<MainViewModel>(new MainViewModel.ParameterThird { Id = "33" });
}
}
In my code, each time the First Init method is called, I'm really at the end and don't have further ideas :) Did anyone here experienced the same issue? Or do anyone here have another Idea to collect data to the main viewmodel in an elegant way? Thanks a lot in advance for reading :)
The Init mechanism in MvvmCross is deliberately lightweight. If you declare multiple methods, all of them will be called - this is by design. Also if some of the Init parameter objects were to share properties then these would clash - see Custom types in Navigation parameters in v3
As it says in the blog post you reference "generally you will probably only want to use one within your application" - so I'd recommend refactoring to a single navigation parameter object and using your own ViewModel-based logic to decide how your ViewModel should initialise.
If you really do need three Init methods called in three different situations, then you can easily pack and unpack your own parameter objects using a custom method (possibly in a BaseViewModel class) like in https://stackoverflow.com/a/19059938/373321

Looking to initialise every view with a List no matter the entry point of the site

I have a Shopping Basket which holds selected items by the user and is stored in a session variable. I'd like this to show various values on every view about the basket state i.e. basket:1, but I can only see how to pass this to a view at a single entry point. How would I initialise every view with this List?
You could render a child action in your Layout. The idea of a child action is that it could execute some logic in parallel to the main action.
For example you could have the following controller:
public class ShoppingBasketInfoController: Controller
{
[ChildActionOnly]
public ActionResult Index()
{
var model = Session["info"] as ShoppingInfoViewModel;
return PartialView(model);
}
}
and then you will have a corresponding partial view (~/Views/ShoppingBasketInfo/Index.cshtml):
#model ShoppingInfoViewModel
<div>
You have #Html.DisplayFor(x => x.NbProducts) in your basket
</div>
and then you could find an appropriate place in your Layout to render this action:
#Html.Action("Index", "ShoppingBasketInfo")
Now all your views will have this information shown at the specified location without worrying about where this information is coming from, how is it stored or what view model it uses. The main actions are completely independent.
I have decorated the child action with the [ChildActionOnly] attribute to ensure that this action will never be accessible through a normal HTTP request from the client using for example /ShoppingBasketInfo/Index. It could only be used within the context of the main executing action.
Your best bet is probably a combination of Base Controller, Base View Model, Interface and Action Filter.
// Interface. To be implemented by model and controller.
public interface IHoldABasket
{
Basket Basket { get; set; };
}
// Base view model. Has a basket as public property.
public BaseBasketViewModel : IHoldABasket
{
public Basket Basket { get; set; }
}
// Base controller model. Also has a basket.
public BaseController : Controller, IHoldABasket
{
public Basket Basket { get; set; }
public BaseController()
{
AttemptBasketLoad();
}
private void AttemptBasketLoad()
{
// Replace the SomeMethodToLoadBasket with whatever method you use
// to retrieve a basket.
Basket = SomeMethodToLoadBasket();
}
}
// Action Filter
public class BasketAwareAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
// If controller can hold basket AND model can hold basket
if (filterContext.Controller is IHoldABasket
&& filterContext.Controller.ViewData.Model is IHoldABasket)
{
// Copy basket from controller into model.
// Will now be accessible through Basket property on model.
((IHoldABasket)filterContext.Controller.ViewData.Model)
.LoggedInUser
= ((IHoldABasket)filterContext.Controller).LoggedInUser;
}
base.OnActionExecuted(filterContext);
}
}
So that's the infrastructure sorted. Let's look at a practical example. You probably have a ProductListViewModel. That should inherit from the base view model class.
First, make sure your ProductListViewModel inherits from BaseBasketViewModel.
public class ProductListViewModel : BaseBasketViewModel
{
}
Because of the inheritance, your view model contains a basket object and implements the IHoldABasket interface.
Your controllers will inherit from BaseController.
public class ProductController : BaseController
{
}
A controller method looks like this.
[BasketAware]
public ViewResult Products(int page = 1)
{
// Load VM that implements IHoldABasket
// Really contrived, I know... :P
var vm = new ProductListViewModel() { Results = productServices.Search() };
return View(vm);
}
That should be it. What's happening under the hood is
Base controller attempts to load a basket, storing it if it finds one
Controller and Model inherit a common interface, making the automated copy from controller to model easier to achieve.
Action filter loads at the last minute. If both controller and model can hold a basket (e.g. both implement IHoldABasket), the basket is copied from controller to model.
All view models that derive from BaseBasketViewModel will have a public property called Basket.

TypedFactoryFacility: how do i initialize an object with an inline parameter?

How can i produce the same output as specified below using the TypedFactoryFacility?
public class Something
{
public void Initialize(Whatever instance) {}
}
public interface ISomethingFactory
{
Something Create(Whatever instance);
}
internal class SomethingFactory : ISomethingFactory
{
private readonly IWindsorContainer _container;
public SomethingFactory(IWindsorContainer container)
{
_container = container;
}
public Something Create(Whatever instance)
{
Something item = _container.Resolve<Something>();
item.Initialize(instance);
return item;
}
}
So I want to replace the manual factory with a proxy-generated ITypedFactoryFacility, but I cant find a way to invoke something on the resolved component after creation. I looked at commission-concerns, but you don't have a reference to the CreationContext from a custom commision concern so that won't work. I could of course move the dependency to the ctor and provide an ctor override, but I think properties are good when you want to convey non-optional dependencies.
You don't need to invoke stuff on the instance upon creation - Windsor will automagically inject stuff when the name of the parameter in the factory method signature matches something that can be injected - be it constructor paramaters or public properties... short example (using a public property):
interface ISomeFactory
{
Something CreateSomething(object dataSource);
}
class Something
{
public object DataSource { get; set; }
}
Given that these are registered like this:
container.Register(Component.For<ISomeFactory>().AsFactory(),
Component.For<Something>().Lifestyle.Transient)
you can resolve instances of Something like this:
var aintThatSomething = someFactory.CreateSomething(new [] {"ZOMG!", "w00t!"});
Remember that if something inside the burden associated with the instance of Something requires decommissioning, you need to provide an appropriate Release method on the factory as well.