So I'm playing around with MvvmCross and Monotouch.Dialog in iOS and I am experiencing a binding issue when I do something that is pretty trivial - and in fact done almost verbatim in one of Stuart's n+1 videos.
Given the following view:
[Register("FirstView")]
public class FirstView : MvxDialogViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
var bindings = this.CreateInlineBindingTarget<FirstViewModel>();
Root = new RootElement("Example Root")
{
new Section("Search")
{
new EntryElement("SearchString", "Search String").Bind(bindings, vm => vm.SearchString)
}
};
}
}
and this ViewModel:
public class FirstViewModel : MvxViewModel
{
private string _searchString = "search string";
public string SearchString
{
get
{
return _searchString;
}
set
{
_searchString = value;
RaisePropertyChanged(() => SearchString);
}
}
}
When navigate to this view, I get the following errors from Mvx:
2013-08-22 14:44:51.766 TestApp[11581:c07] MvxBind:Error: 2.02 Empty binding target passed to MvxTargetBindingFactoryRegistry
[0:] MvxBind:Error: 2.02 Empty binding target passed to MvxTargetBindingFactoryRegistry
[0:]
2013-08-22 14:44:51.869 TestApp[11581:c07] MvxBind:Warning: 2.10 Failed to create target binding for to
[0:] MvxBind:Warning: 2.10 Failed to create target binding for to
I'm not exactly sure why the binding is failing. If I set a break point in the "Get" for SearchString, I do in fact see it getting hit. Changing the value of the Entry Element however does not trigget the "Set".
Any thoughts?
Stuart's guess is correct:
All I needed to do was change my Setup class to inherit from MvxTouchDialogSetup.
Related
I'm trying to hide an ui element on view load. I'm using Xamarin iOS and MvvmCross (6.2.3.0). In my view, I create an uitextfield and add following binding:
public override void ViewDidLoad()
{
//adding uitextfield
var set - this.CreateBindingSet(MyViewConroller, MyViewModel>();
set.Bind(uitextfield).For("Visibility").To(vm => vm.FieldVisibility).TwoWay().WithConversion("Visibility");
}
And here is my viewmodel:
public MyViewModel : MvxViewModel
{
private bool _fieldVisibility;
public bool FieldVisibility
{
get {return _fieldVisibility;}
set {
SetProperty(ref _fieldVisibility, value);
}
public override Task Initialize() {
FieldVisibility = false;
}
But when the view is loaded, my uitextfield is still visible. I tried to call RaisePropertyChanged, but it didn't help:
private bool _fieldVisibility;
public bool FieldVisibility
{
get {return _fieldVisibility;}
set {
SetProperty(ref _fieldVisibility, value);
RaisePropertyChanged(nameof(FieldVisibility));
}
It works, when I change some other viewmodel property and simply call:
private string _otherProperty;
public string OtherProperty
{
get {return _otherProperty;}
set {
SetProperty(ref _otherProperty, value);
FieldVisibility = false;
RaisePropertyChanged(nameof(FieldVisibility));
}
but I'd like to have this uitextfield hidden on view load. Can you suggest any solution for that? I'd be very grateful for any hint. Thanks!!
We have strongly typed bindings nowadays for stuff like this:
set.Bind(uitextfield).For(v => v.BindVisible()).To(vm => vm.FieldVisibility);
You don't really need the visibility converter here, should work without it.
Remember to also call Apply() on your binding set.
Also TwoWay() won't do anything here, because there are no Binding Targets which allow for Two Way bindings for UIView visibility.
Structuremap experts,
I found this post on stackoverflow ...
Passing constructor arguments when using StructureMap
Someone suggested to use the StructureMap configuration with runtime value like this
For<IProductProvider>().Use<ProductProvider>.Ctor<string>("connectionString").Is(someValueAtRunTime);
But example is not adequate enough to understand its declaration and usage. I try to find on StructureMap site as well but not much help ...
In my situation, I want to pass on the dependency of concrete DbContext (IDbContext) to the constructor of the class with connection string dynamically created during run time within that class.
Finally I managed to make it working ...
Here how I did it ...
Hope it will help someone, and thanks to PHeiberg for answer me before and showing me right direction.
Interface Definition
public interface ICreditCard
{
string GetName();
}
public interface IAdditionalCreditCard : ICreditCard
{
}
public class AdditionalCreditCard : IAdditionalCreditCard
{
private readonly string _name;
public AdditionalCreditCard(string name)
{
_name = name;
}
public string GetName()
{
return _name;
}
}
Define function in Structure map config code
Func<string, IAdditionalCreditCard> additionalCreditCard = value =>
ObjectFactory.With("name").EqualTo(value).GetInstance<AdditionalCreditCard>();
Add following configuration in ObjectFactory.Configure
ObjectFactory.Configure(config =>
{
config.For<Func<string, IAdditionalCreditCard>>().Use(additionalCreditCard);
});
And in code ...
public class PaymentSystem
{
private readonly Func<string, IAdditionalCreditCard> _addtionalCreditCard;
private IAdditionalCreditCard _addCreditCard;
public PaymentSystem(Func<string, IAdditionalCreditCard> additionalCredit)
{
_addtionalCreditCard = additionalCredit;
}
public string AddtionalSystemType()
{
_addCreditCard = _addtionalCreditCard("American Express");
return _addCreditCard.GetName();
}
}
The code you are posting is supposed to go in the setup code for StructureMap, which can go in the Initialize/Configure method or a Registry. The setup code is normally executed only once in the application's life cycle. So if you know the connection string value when the application is stared and you configure StructureMap, you can put the code you posted in the initialization of StructureMap. If the value is not known until later on, you need some kind of factory approach.
A factory approach could be done like this (in your StructureMap configuration code):
Func<string, IDbContext> createContext = value => {
/* create context based on value */
};
ObjectFactory.Initialize(c => {
For<Func<string, IDbContext>>().Use(createContext);
// The rest of you configuration ...
});
You can now use the Func to create an instance of the context when you need it:
public class ProductProvider : IProductProvider
{
private readonly Func<string, IDbContext> _contextCreator;
public ProductProvider(Func<string, IDbContext> contextCreator)
{
_contextCreator = contextCreator;
}
public IEnumerable<Product> GetProducts(string someValue)
{
using(var context = contextCreator(someValue))
{
return SomeOperationOnThe(context);
}
}
}
What I am trying to achieve here is a global loading indicator with MvvmCross.
From what I gathered so far it appears that I can implement this by using a BaseView and BaseViewModel. The BaseViewModel should contain an IsLoading property that the BaseView can Bind to. Therefore, I can set IsLoading to true in any of my ViewModels to cause the indicator to be displayed.
The BaseViewModel looks like this:
public abstract class BaseViewModel : MvxViewModel
{
private bool _isLoading = false;
public bool IsLoading
{
get { return _isLoading; }
set { _isLoading = value; RaisePropertyChanged(() => IsLoading); }
}
private string _loadingMessage = "Loading...";
public string LoadingMessage
{
get { return _loadingMessage; }
set { _loadingMessage = value; RaisePropertyChanged(() => LoadingMessage); }
}
}
As for binding to the ViewModel, this issue was addressed here: https://stackoverflow.com/a/10930788
I was successfully able to listen to IsLoading by attaching to the PropertyChanged event, however, since this is not a "real" binding it will not fire if IsLoading is set to true before you attach to the event (Such as before the view is loaded).
Next I attempted to call AddBindings to attach to my BaseViewModel in order to create a real binding between the two properties, although this does not appear to work (no errors).
Here is what my BaseView currently looks like for iOS:
public abstract class BaseView<TViewModel> : MvxViewController where TViewModel : BaseViewModel
{
public TViewModel ViewModel
{
get { return (TViewModel)base.ViewModel; }
set { base.ViewModel = value; }
}
protected BaseView(string nib, NSBundle bundle) : base(nib, bundle)
{
}
private LoadingOverlay _loadingOverlay;
public override void ViewDidLoad()
{
base.ViewDidLoad();
_loadingOverlay = new LoadingOverlay(View.Frame);
this.AddBindings(new Dictionary<object, string>()
{
{this, "{'IsLoading':{'Path':'IsLoading'}}"},
{_loadingOverlay, "{'LoadingMessage':{'Path':'LoadingMessage'}}"}
});
}
public bool IsLoading
{
set
{
if (value)
View.Add(_loadingOverlay);
else
_loadingOverlay.Hide();
}
}
}
Are there any pitfalls to AddBindings that I may be unaware of? or is there a newer method altogether that I should be using?
I Appreciate the help, Thanks.
If you are using v3, then by default you must change over to the new 'Swiss' style bindings.
These change Json text like:
"{'LoadingMessage':{'Path':'LoadingMessage'}}"
Into simpler text like:
"LoadingMessage LoadingMessage"
For more on this, see http://blog.ostebaronen.dk/2013/01/awesome-mvvmcross-swiss-bindings-for.html
One further option you can use in v3 is 'fluent bindings'. To see these in action, see all the iOS samples in the N+1 series - http://mvvmcross.wordpress.com/, or questions like Fluent Bindings and UIButton titles
Eg something like:
this.CreateBinding().For("LoadingMessage").To("LoadingMessage").Apply();
It may also help for debugging if you enable additional trace - see MvvmCross Mvx.Trace usage
I am building a framework that I don't want to couple to a particular IOC container so have created a layer on top of Ninject / structuremap etc.
I have a binding class that accepts a Func to allow binding to a method.
For example
public class Binding
{
public Type Source { get; set; }
public Func<object> Method {get; set; }
public Scope { get; set; }
}
If I have a binding like...
var binding = new Binding() {
Source = typeof(IRepository),
Method = () => new Repository(new LinqToSqlRepository(connectionString)),
Scope = Scope.HttpRequest
};
The framework wrapping Ninject creates Ninject bindings for my generic binding like this
Module :NinjectModule
{
IList<Binding> _Bindings;
public Module(IList<Binding> bindings)
{
_Bindings = bindings;
}
public override void Load() {
foreach (var binding in _Bindings) {
switch(binding.Scope) {
case IocScope.HttpRequest:
Bind(binding.Source).ToMethod(c => binding.Method()).InRequestScope();
break;
// ... omitted for brevity
}
}
}
}
This works fine when there is only one binding being bound to a method. When there are multiple bindings being bound within the same module to methods however the incorrect type is returned. From debugging, it looks as if the last binding is always used.
Thus the problem with an example;
var binding1 = new Binding() {
Source = typeof(IRepository),
Method = () => new Repository(new LinqToSqlRepository(connectionString)),
Scope = Scope.HttpRequest
};
var binding2 = new Binding() {
Source = typeof(ICalendar),
Method = () => new MvcCalendar( ..... )
Scope = Scope.HttpRequest
};
At runtime when Ninject is requested to new up an MVC Controller which takes in an IRepository and an ICalendar, I receive a type conversion error saying that a MvcCalendar cannot be converted to an IRepository. I have discovered that for some reason the last binding is always being returned for the first requested type.
This is a highly simplified version of what is really going on to try and highlight the actual issue, the wrong method being bound to a requested type when there are multiple method bindings. I hope this still explains the issue though.
This appears to be related to some sort of closure scoping issue. I also wonder whether Ninject is getting is getting confused by the Func instead of Func usage.
Unit Test Example
Here is a test module I load into my custom IOC container. This does not depend on any particular IOC framework. When I instantiate a NinjectIocContainer to handle the DI, the internal binding of this in Ninject occurs as example further up (see NinjectModule)
public class MultipleMethodBoundTypesModule : IocModule
{
public override void Load()
{
Bind<IPerson>().To(() => new Person()).In(IocScope.Transient);
Bind<IRobot>().To(() => new Robot(new Person())).In(IocScope.Transient);
}
}
Here is a simple test that tries to retrieve each of the types.
[Test]
public void Expect_That_Multiple_Method_Bound_Types_Can_Exist_Within_The_Same_Module()
{
// arrange
var container = Get_Container_With_Module(new MultipleMethodBoundTypesModule());
// act
var person = container.Get<IPerson>();
var robot = container.Get<IRobot>();
// assert
Assert.IsNotNull(person);
Assert.IsNotNull(robot);
}
As explained eariler, this throws a type conversion where the last closure (for the robot) is being bound to a person.
TestCase 'Ioc.Test.NinjectContainerTest.Expect_That_Multiple_Method_Bound_Types_Can_Exist_Within_The_Same_Module'
failed: System.InvalidCastException : Unable to cast object of type 'Ioc.Test.Robot' to type 'Ioc.Test.IPerson'.
at System.Linq.Enumerable.d__b11.MoveNext()
at System.Linq.Enumerable.Single[TSource](IEnumerable1 source)
at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, IParameter[] parameters)
NinjectIocContainer.cs(40,0): at Ioc.Ninject.NinjectIocContainer.GetTInstance
IocTestBase.cs(149,0): at Ioc.Test.IocTestBase.Expect_That_Multiple_Method_Bound_Types_Can_Exist_Within_The_Same_Module()
In the snippet:
Bind(binding.Source).ToMethod(binding.Method()).InRequestScope();
You're dereferencing the Method bit. You want to be doing that as either binding.Method or ()=>binding.Method() (the former may not unambiguously be inferrable based on the C# type inference rules).
You mentioned this is heavily stripped down from your real code. As a result, this may not be the actual issue. I'd still be betting on some form of closure confusion though (see the section Comparing capture strategies: complexity vs power in this CSID excerpt for a nice walkthrough).
You also probably meant to use .InScope(binding.Scope) rather than .InRequestScope() too,.
TextView has some DrawableXXX properties like DrawableStart, DrawableEnd, DrawableTop, etc. to show a Drawable next to the TextView text. Is it possible to bind this property using MvvmCross? I've tried using local:MvxBind="Drawablename 'check'" in my binding but it fails showing an MvxBind error:
Failed to create target binding for binding DrawableName for check
If this binding is not implemented in MvvmCross then how would I go about doing this on my own? Is the method used by N+28 still the recommended way to add a custom binding?
There is not currently any MvvmCross define target binding for adding compound drawables. However, you can easily add your own. If you are using MvvmCross 5+ you can take advantage of the new typed custom binding classes. Note, the example below has set the drawable to be placed on the left, however, you can choose to place at any direction (or multiple).
public class CompoundDrawablesDrawableNameBinding : MvxAndroidTargetBinding<TextView, string>
{
public const string BindingIdentifier = "CompoundDrawableName";
public CompoundDrawablesDrawableNameBinding(TextView target) : base(target)
{
}
public override MvxBindingMode DefaultMode => MvxBindingMode.OneWay;
protected override void SetValueImpl(TextView target, string value)
{
var resources = AndroidGlobals.ApplicationContext.Resources;
var id = resources.GetIdentifier(value, "drawable", AndroidGlobals.ApplicationContext.PackageName);
if (id == 0)
{
MvxBindingTrace.Trace(MvxTraceLevel.Warning,
"Value '{0}' was not a known compound drawable name", value);
return;
}
target.SetCompoundDrawablesWithIntrinsicBounds(
left: id,
top: 0,
right: 0,
bottom: 0);
}
}
Then register in your Setup.cs
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterCustomBindingFactory<TextView>(
CompoundDrawablesDrawableNameBinding.BindingIdentifier,
textView => new CompoundDrawablesDrawableNameBinding(textView));
}
Then in your XML you can use
local:MvxBind="CompoundDrawableName <<YOUR PROPERTY TO BIND TO>>"