I am trying to bind to the badgevalue of a TabBarItem like this:
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(ViewControllers[0].TabBarItem.BadgeValue).To(vm => vm.MyNumber);
set.Apply();
but I get the following error:
MvxBind: Error: 6.30 Empty binding target passed to MvxTargetBindingFactoryRegistry
However if I set the value directly like this the badge appears:
ViewControllers[0].TabBarItem.BadgeValue = ((MyViewModel)ViewModel).MyNumber;
Why is the binding not working?
Thanks!
This doesn't work because you need to set up your own binding extensions if what you are trying to bind to doesn't exist as a valid binding target already.
Look here where Stuart answered a similar question for Android.
Create a custom binding:
public class TabBadgeBinding : MvxTargetBinding
{
public const string TargetPropertyName = nameof(TabBadgeBinding);
private readonly UIViewController _viewController;
public TabBadgeBinding(UIViewController viewController) : base(viewController)
{
_viewController = viewController;
}
public override Type TargetType => typeof(UIViewController);
public override MvxBindingMode DefaultMode => MvxBindingMode.OneWay;
public override void SetValue(object value)
{
if (value is string badgeValue)
{
_viewController.TabBarItem.BadgeValue = badgeValue;
}
}
}
Register Binding in Setup.cs:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterFactory(new MvxCustomBindingFactory<UIViewController>(TabBadgeBinding.TargetPropertyName, (controller) => new TabBadgeBinding(controller)));
}
Use Binding in your VC:
set.Bind(ParentViewController).For(TabBadgeBinding.TargetPropertyName).To(vm => vm.UnreadNotificationsCount);
Related
I have a template class that is in its own namespace and that I add to my code with `
new InfoLabel()`{ Text = "abc" };
Note that this is just a very simple example and I have other template objects that don't just depend on one thing, for example an object with 2-3 labels.
Is there a way that I can apply Xamarin C# fluent to create a templated object?
Here is the simple example object that I have:
namespace Test
{
public class InfoLabel : Label
{
public InfoLabel()
{
SetDynamicResource(FontFamilyProperty, Const.Fonts.DefaultRegular);
SetDynamicResource(FontSizeProperty, Const.Fonts.InfoTextFontSize);
SetDynamicResource(TextColorProperty, Const.Colors.InfoLabelColor);
LineBreakMode = LineBreakMode.WordWrap;
VerticalOptions = LayoutOptions.Start;
HorizontalTextAlignment = TextAlignment.Start;
}
}
}
What I would like to know is how I can set up the same thing using the latest C# fluent standards?
Here is the way I think it might be done. I used a Build() method but I would appreciate if someone more skilled than me could tell me if I am doing it correctly as this is a big change from what I am used to:
namespace Test
{
public class InfoLabel
{
public InfoLabel()
{
Build();
}
void Build() =>
new Label
{
LineBreakMode = LineBreakMode.WordWrap,
}
.TextLeft()
.DynamicResources((Label.FontFamilyProperty, Const.Fonts.DefaultRegular),
(Label.FontSizeProperty, Const.Fonts.InfoTextFontSize),
(Label.TextColorProperty, Const.Colors.InfoLabelColor));
Here is another idea that I have:
namespace Test
{
public class InfoLabel : Label
{
public InfoLabel()
{
LineBreakMode = LineBreakMode.WordWrap;
Build();
}
void Build() =>
this.TextLeft()
.DynamicResources((Label.FontFamilyProperty, Const.Fonts.DefaultRegular),
(Label.FontSizeProperty, Const.Fonts.InfoTextFontSize),
(Label.TextColorProperty, Const.Colors.InfoLabelColor));
Note that I am using an extension method for the resources.
You could create the instance of label like following
public class InfoLabel : Label
{
static InfoLabel CreateDefaultLabel()
{
return new InfoLabel
{
LineBreakMode = LineBreakMode.WordWrap,
}
.TextLeft()
.DynamicResources((Label.FontFamilyProperty, Const.Fonts.DefaultRegular),
(Label.FontSizeProperty, Const.Fonts.InfoTextFontSize),
(Label.TextColorProperty, Const.Colors.InfoLabelColor));
}
}
var label = InfoLabel.CreateDefaultLabel();
For more details of the usage of markup you could check this blog .
I am trying to find a way to be able to set from the View to what ViewModel I have to navigate. This is to be able to change the navigation flow without changing the core project.
I thought the easier way would be creating an interface, setting the target ViewModel there and injecting the interface into the ViewModel to then perform the navigation.
public interface IModelMapping
{
MvxViewModel ViewModelToNavigate();
}
public class MyViewModel : MvxViewModel
{
readonly IMvxNavigationService navigationService;
readonly IModelMapping modelMapping;
public MyViewModel(IMvxNavigationService navigationService, IModelMapping modelMapping)
{
this.navigationService = navigationService;
this.modelMapping = modelMapping;
}
public IMvxAsyncCommand GoContent
{
get
{
IMvxViewModel vm = modelMapping.ViewModelToNavigate();
IMvxAsyncCommand navigateCommand = new MvxAsyncCommand(() => navigationService.Navigate<vm>());
return navigteCommand;
}
}
}
The problem with this code is I am getting an error setting the navigationService.Navigate(). The error is 'vm is a variable but it is used like a type'
What about using the URI navigation together with the facade? See also https://www.mvvmcross.com/documentation/fundamentals/navigation#uri-navigation
Say you are building a task app and depending on the type of task you want to show a different view. This is where NavigationFacades come in handy (there is only so much regular expressions can do for you).
mvx://task/?id=00 <– this task is done, show read-only view (ViewModelA)
mvx://task/?id=01 <– this task isn’t, go straight to edit view (ViewModelB)
[assembly: MvxRouting(typeof(SimpleNavigationFacade), #"mvx://task/\?id=(?<id>[A-Z0-9]{32})$")]
namespace *.NavigationFacades
{
public class SimpleNavigationFacade
: IMvxNavigationFacade
{
public Task<MvxViewModelRequest> BuildViewModelRequest(string url,
IDictionary<string, string> currentParameters, MvxRequestedBy requestedBy)
{
// you can load data from a database etc.
// try not to do a lot of work here, as the user is waiting for the UI to do something ;)
var viewModelType = currentParameters["id"] == Guid.Empty.ToString("N") ? typeof(ViewModelA) : typeof(ViewModelB);
return Task.FromResult(new MvxViewModelRequest(viewModelType, new MvxBundle(), null, requestedBy));
}
}
}
I am facing a very weird issue, I am showing categories to user using horizontal CollectionView in Xamarin.iOS and i am using MvvMCross as well. The issue is that that when i scroll the categories CollectionView and scroll back some of the cells are empty.
This is the code for the subclass of the MvxCollectionViewCell in which i am binding values to the UI.
public partial class DealListCategoryCollectionCellView : MvxCollectionViewCell
{
public static readonly UINib Nib = UINib.FromName("DealListCategoryCollectionCellView", NSBundle.MainBundle);
public static readonly NSString Key = new NSString("CategoryCollectionViewCell");
private FXMvxImageViewLoader _imageLoader;
public DealListCategoryCollectionCellView(IntPtr handle) : base(handle)
{
_imageLoader = new FXMvxImageViewLoader(() => CategoryImageView);
this.DelayBind(() =>
{
var set = this.CreateBindingSet<DealListCategoryCollectionCellView, CategoryCellViewModel>();
set.Bind(TitleLabel).To(vm => vm.Name);
set.Bind(_imageLoader).To(vm => vm.Slug).WithConversion("LocalImage", "icons");
set.Apply();
});
}
public static DealListCategoryCollectionCellView Create()
{
return (DealListCategoryCollectionCellView)Nib.Instantiate(null, null)[0];
}
}
And this is how i am binding the above collectionview Cell.
CategoryCollectionView.RegisterNibForCell(DealListCategoryCollectionCellView.Nib, DealListCategoryCollectionCellView.Key);
var categorySource = new MvxCollectionViewSource(CategoryCollectionView,DealListCategoryCollectionCellView.Key);
CategoryCollectionView.Source = categorySource;
And this is the screenshot of the issue :
I have fixed the issue by first changing this:
CategoryCollectionView.Source = categorySource;
to:
CategoryCollectionView.DataSource = categorySource;
So when i changed from Source to DataSource the empty cell issue was fixed but there was another issue my tap on cell was not being fired. So i implemented CollectionViewDelegate like this:
class CategoryCollectionDelegate : UICollectionViewDelegate
{
DealListViewModel viewModel;
public CategoryCollectionDelegate(DealListViewModel vm)
{
viewModel = vm;
}
public override void ItemSelected(UICollectionView collectionView, NSIndexPath indexPath)
{
Category currentCategory = viewModel.Categories[indexPath.Row];
Console.WriteLine(currentCategory.Name);
viewModel.DoSelectCategoryCommand(currentCategory);
}
}
And than i used it like that :
CategoryCollectionView.Delegate = new CategoryCollectionDelegate(DealListViewModel);
And it worked like charm :)
the solution for me was to overwrite the CellDisplayingEnded method in the CollectionViewSource. As you see here https://github.com/MvvmCross/MvvmCross/blob/develop/MvvmCross/Binding/iOS/Views/MvxBaseCollectionViewSource.cs there is some commented code in CellDisplayingEnded method which maybe in some version of mvvmcross is not commented and dataContext is set to null.
I just upgraded a project from an older version of MvvmCross to the latest version, and I'm having trouble with bindings.
I'm aware of the LinkerPleaseInclude hack, and it looks like (at least some of) the properties I'm using are listed there.
I'm concerned about my usage of the Views and ViewModels. Here's an example.
public partial class HomeView : MvxViewController<HomeViewModel>
...
public override void ViewDidLoad ()
{
base.ViewDidLoad();
this.CreateBinding(BudgetTrackerButton).To((HomeViewModel vm) => vm.BudgetTracker).Apply();
this.CreateBinding(LoginButton).For("Title").To ((HomeViewModel vm) => vm.LogMessage).Apply();
this.CreateBinding(LoginButton).To((HomeViewModel vm) => vm.Login).Apply();
this.CreateBinding(ContactApprisenButton).To((HomeViewModel vm) => vm.Contact).Apply();
this.CreateBinding(AboutApprisenButton).To((HomeViewModel vm) => vm.About).Apply();
this.CreateBinding(AboutThisAppButton).To((HomeViewModel vm) => vm.AboutApp).Apply();
this.CreateBinding(FAQsButton).To((HomeViewModel vm) => vm.FAQs).Apply();
this.CreateBinding(PrivacyPolicyButton).To((HomeViewModel vm) => vm.PrivacyPolicy).Apply();
}
public abstract class MvxViewController<T> : UIViewController, IMvxBindingContextOwner, IUIWrappable, MvvmCross.iOS.Views.IMvxIosView where T : ViewModelBase
...
}
protected MvvmCross.Core.ViewModels.IMvxViewModel _viewModel = null;
public MvvmCross.Core.ViewModels.IMvxViewModel ViewModel {
get {
if (_viewModel == null) {
_viewModel = MvvmCross.Platform.Mvx.Resolve<T> ();
}
return _viewModel;
}
set {
_viewModel = value;
}
}
public MvvmCross.Core.ViewModels.MvxViewModelRequest Request {
get;
set;
}
public object DataContext {
get {
return _viewModel;
}
set {
_viewModel = (MvvmCross.Core.ViewModels.IMvxViewModel)value;
}
}
protected MvvmCross.Binding.BindingContext.IMvxBindingContext _bindingContext;
public IMvxBindingContext BindingContext {
get {
if (_bindingContext == null) {
_bindingContext = new MvvmCross.Binding.BindingContext.MvxBindingContext ();
}
return _bindingContext;
}
set {
_bindingContext = value;
}
}
CreateBinding is from the MvxBindingContextOwnerExtensions.
When I hit CreateBinding, the view model has been made.
I understand DataContext to be the same as the ViewModel, and I only include it to conform to the MvvmCross.iOS.Views.IMvxIosView interface.
Am I missing a step somewhere? An interface?
Matching views to view models should happen automatically based on naming conventions, right? (They didn't.. in my case, I had to manually specify the mappings in my Setup class. Could be contributing to this problem.)
Strangely enough, this works (for the button title anyway, I haven't tested the other bindings, and I'm not interested in updating all the bindings across the entire application if the fix is simple):
var set = this.CreateBindingSet<HomeView, HomeViewModel>();
set.Bind(LoginButton).For("Title").To(vm => vm.LogMessage);
I can post more code if something else would be relevant. I'm also new to MvvmCross.
I am trying to create my first Xamarin.Forms custom user control named LocationSelector. It has an Entry and when the user enters something, a list with selectable locations is shown (similary to the selection in Google Maps).
The selected location is the important 'return value' of the control.
My plan is to catch the ItemSelected event of the list and set the SelectedLocation property. LocationSelector is designed as MVVM and since everything is working so far here just the Code-Behind (which I think is enough to describe the problem):
public partial class LocationSelector : StackLayout
{
public static readonly BindableProperty SelectedLocationProperty =
BindableProperty.Create<LocationSelector, LocationModel>(s => s.SelectedLocation, new LocationModel(), BindingMode.TwoWay);
public LocationSelector()
{
InitializeComponent();
var model = new LocationSelectorModel();
BindingContext = model;
_listView.ItemSelected += (sender, args) =>
{
SelectedLocation = model.SelectedLocation;
};
}
public LocationModel SelectedLocation
{
get { return (LocationModel)GetValue(SelectedLocationProperty); }
set { SetValue(SelectedLocationProperty, value); }
}
}
Now I want to use this control on a search view where the BindingContext is set to the SearchViewModel:
<ContentPage x:Class="Application.App.Views.SearchView" ...>
<c:LocationSelector SelectedLocation="{Binding Location}"/>
</ContentPage>
public class SearchViewModel : ViewModel
{
private LocationModel _location;
public LocationModel Location
{
get { return _location; }
set { SetProperty(ref _location, value); }
}
}
Unfortunately this is not working. The output throws a binding warning:
Binding: 'Location' property not found on 'Application.App.CustomControls.LocationSelectorModel', target property: 'Application.App.CustomControls.LocationSelector.SelectedLocation'
Why points the binding to a property in the ViewModel that is used 'within' my custom control and not to the property in the BindingContext of the view?
The problem is setting the BindingContext to the view model of the user control:
public LocationSelector()
{
var model = new LocationSelectorModel();
BindingContext = model; // this causes the problem
// ...
}
In this post I found the solution. Setting the BindingContext to each child elements rather than the whole user control is doing the job:
public LocationSelector()
{
var model = new LocationSelectorModel();
foreach (var child in Children)
{
child.BindingContext = model;
}
}