Issue in Binding BEMCheckBox to ViewModel - MvvmCross, Xamarin.iOS - c#

I am new to MvvmCross and still figuring out how things are done. I am using BEMCheckBox for one of my developing applications and I tried to bind BEMCheckBox's "On" property to ViewModel. Unfortunately, it is not working as expected.
BEMCheckBox
Github : https://github.com/saturdaymp/XPlugins.iOS.BEMCheckBox
NuGet : SaturdayMP.XPlugins.iOS.BEMCheckBox
Version: 1.4.3
MvvmCross
Version : 6.4.2
This is my View Class
[MvxFromStoryboard("Main")]
[MvxRootPresentation(WrapInNavigationController = false)]
public partial class MyView : BaseView<MyViewModel>
{
private BEMCheckBox CheckBox;
public MyView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
//add check box
CheckBox = new BEMCheckBox(new CoreGraphics.CGRect(0, 0, 20, 20))
{
BoxType = BEMBoxType.Square,
TintColor = UIColor.FromRGBA(0, 0, 0, 0.14f),
OnFillColor = UIColor.FromRGB(42, 183, 202),
OnCheckColor = UIColor.White,
OnTintColor = UIColor.FromRGBA(0, 0, 0, 0.14f),
OnAnimationType = BEMAnimationType.Bounce,
OffAnimationType = BEMAnimationType.Bounce,
On = false,
CornerRadius = 0,
};
//CheckBoxContainerView is an UIView
CheckBoxContainerView.AddSubview(CheckBox);
CheckBoxContainerView.BackgroundColor = UIColor.Clear;
//Binding to View Model
var set = this.CreateBindingSet<MyView, MyViewModel>();
set.Bind(CheckBox).For(c => c.On).To(vm => vm.IsCheckBoxOn);
set.Apply();
}
}
This is my ViewModel Class
public class MyViewModel : BaseViewModel
{
private bool _isCheckBoxOn;
public bool IsCheckBoxOn
{
get => _isCheckBoxOn;
set
{
SetProperty(ref _isCheckBoxOn, value);
Console.WriteLine(_isCheckBoxOn);
}
}
public MyViewModel : base()
{
}
public override Task Initialize()
{
return base.Initialize();
}
}
When I do the same thing for UISwitch it works perfectly. Any help would be highly appreciated.

MvvmCross includes many target bindings out of the box, UISwitch being one of them. For a complete list see "built-in-bindings" in MvvmCross documentation.
One-way binding (ViewModel -> View)
By default, you can create a binding to assign a value to a property on your view from your view model, this is called one-way binding. The approach works even with custom 3rd party controls, as long as the types match.
Two-way binding (ViewModel -> View && View -> ViewModel)
In order to do a two-way bind, MvvmCross needs a mechanise for the view to notify the view model that a value of the view has changed. For this MvvmCross using target bindings. See MvvCross documentation for creating custom target bindings.
Example
See MvvmCross source code for MvxUISwitchOnTargetBinding.cs which shows how they do the binding for the UISwitch control and the On state.
public class MvxUISwitchOnTargetBinding : MvxTargetBinding<UISwitch, bool>
{
private IDisposable _subscription;
public MvxUISwitchOnTargetBinding(UISwitch target)
: base(target)
{
}
protected override void SetValue(bool value)
{
Target.SetState(value, true);
}
public override void SubscribeToEvents()
{
var uiSwitch = Target;
if (uiSwitch == null)
{
MvxBindingLog.Error( "Error - Switch is null in MvxUISwitchOnTargetBinding");
return;
}
_subscription = uiSwitch.WeakSubscribe(nameof(uiSwitch.ValueChanged), HandleValueChanged);
}
public override MvxBindingMode DefaultMode => MvxBindingMode.TwoWay;
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (!isDisposing) return;
_subscription?.Dispose();
_subscription = null;
}
private void HandleValueChanged(object sender, EventArgs e)
{
FireValueChanged(Target.On);
}
}
Note the HandleValueChanged method which calls the FireValueChanged() method, this is the method that passes the value that you want to send back up to your bound view model.
You will then need to register your custom target bindings in your Setup.cs class by overriding the FillTargetFactories.

Related

Transition between two viewcontroller XAMARIN.IOS

i am trying to create a transaction between two view controllers on xamarin.ios (i am not using the navigation controllers but the viewcontrollers), i would like that when the swipe gesture is in progress it appears under the view controller that moves another side viewcontroller (the target one) I'm trying to emulate the classic "go back" gesture of ios present both on the system itself and on whatsapp. I had already posted and I had partially solved, managing the scrolling animation, but for the second view controller nothing to do :(
my code...
private void InteractiveTransitionRecognizerActionWithoutIndex(UIScreenEdgePanGestureRecognizer sender, UIView View, string x)
{
//Contiene un valore numerico che varia in base allo stato della gesture
var percento = sender.TranslationInView(View).X * 100 / sender.View.Bounds.Size.Width;
var storyboard = UIStoryboard.FromName("Main", null);
// var viewController = storyboard.InstantiateViewController(x);
//Quando la gesture rileva una variazione
if (sender.State == UIGestureRecognizerState.Changed)
{
var minTransform = CGAffineTransform.MakeTranslation(sender.TranslationInView(View).X, 0); //*2 = piu rapido
var maxTransform = CGAffineTransform.MakeTranslation(sender.TranslationInView(View).X, 0);
View.Transform = true ? minTransform : maxTransform;
UIView.Animate(0.1, 0, UIViewAnimationOptions.CurveEaseInOut,
() =>
{
View.Transform = true ? maxTransform : minTransform;
//???
},
null
);
If you are using storyboards and familiar with segues. To define a custom transition animation you will have to configure your segue as follows:
Then in the first view controller you can override the PrepareForSegue method:
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
base.PrepareForSegue(segue, sender);
var destinationVC = segue.DestinationViewController as SecondViewController;
destinationVC.Callee = this;
destinationVC.TransitioningDelegate = new CustomTransitioningDelegate(sender as UIView);
destinationVC.ModalPresentationStyle = UIModalPresentationStyle.Custom;
}
public class CustomTransitioningDelegate: UIViewControllerTransitioningDelegate
{
readonly UIView _animationOrigin;
public CustomTransitioningDelegate(UIView animationOrigin)
{
_animationOrigin = animationOrigin;
}
public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForPresentedController(UIViewController presented, UIViewController presenting, UIViewController source)
{
var customTransition = new GrowTransitionAnimator(_animationOrigin);
return customTransition;
}
}
The animations are defined in a class that inherit from UIViewControllerAnimatedTransitioning.
public class GrowTransitionAnimator : UIViewControllerAnimatedTransitioning
{
readonly UIView _animationOrigin;
public GrowTransitionAnimator(UIView animationOrigin)
{
_animationOrigin = animationOrigin;
}
public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
// The animation here
}
public override double TransitionDuration(IUIViewControllerContextTransitioning transitionContext)
{
return 0.3;
}
}

Using new ViewModel each time I open a page

I didn't know how better to word the title so I went with solution that came to my mind.
Here is the problem. I have a page that has list and each item on the lists opens a detail page (on click). But the VM is reused, which causes me several problems.
Previous data can be seen for split second when opening a the detail page
I need certain properties to be set to specific values when the page open, but since the VM is reused it keeps all the values from the previous detail and this messes up my logic.
This UWP app. I'm using Template10 framework's NavigationService to move between pages.
Main Page ViewModel
public class MainPageViewModel : ViewModelBase {
private List<MangaItem> _mangaList;
public List<MangaItem> mangaList {
get { return _mangaList; }
set { Set(ref _mangaList, value); }
}
private string _mainSearchText;
public string mainSearchText {
get { return _mainSearchText; }
set { Set(ref _mainSearchText, value); }
}
public MainPageViewModel() {
_mangaList = new List<MangaItem>();
mangaList = new List<MangaItem>();
Initialize();
}
private async void Initialize() {
mangaList = await MangaListGet.GetListAsync();
}
public async void MainSearchSubmitted() {
mangaList = await MangaListGet.GetListAsync(_mainSearchText);
}
public void MangaSelected(object sender, ItemClickEventArgs e) {
var mangaItem = (MangaItem)e.ClickedItem;
NavigationService.Navigate(typeof(Views.MangaDetail), mangaItem.id);
}
}
And Detail Page ViewModel
class MangaDetailViewModel : ViewModelBase {
private MangaItem _mangaDetail;
public MangaItem mangaDetail {
get { return _mangaDetail; }
set { Set(ref _mangaDetail, value); }
}
private string _mangaId;
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> suspensionState) {
_mangaId = parameter as string;
Initialize();
await Task.CompletedTask;
}
private async void Initialize() {
mangaDetail = await MangaDetailGet.GetAsync(_mangaId);
}
public void ChapterSelected(object sender, ItemClickEventArgs e) {
var _chapterId = (ChapterListItem)e.ClickedItem;
NavigationService.Navigate(typeof(Views.ChapterPage), _chapterId.id);
}
}
This code only shows the first problem is displaying previously loaded data for a split second. If needed I will add code that showcases the other problem, but I' not sure if it's really relevant right now. I'm thinking that maybe my entire logic is flawed or something.
EDIT:
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
where vm is xmlns:vm="using:MangaReader.ViewModels".
Another solution is to use Bootstrapper.ResolveforPage() which is intended to handle dependency injection but would easily serve your needs. Like this:
[Bindable]
sealed partial class App : BootStrapper
{
static ViewModels.DetailPageViewModel _reusedDetailPageViewModel;
public override INavigable ResolveForPage(Page page, NavigationService navigationService)
{
if (page.GetType() == typeof(Views.DetailPage))
{
if (_reusedDetailPageViewModel == null)
{
_reusedDetailPageViewModel = new ViewModels.DetailPageViewModel();
}
return _reusedDetailPageViewModel;
}
else
{
return null;
}
}
}
The NavigationService will treat this the same as any other view-model. Meaning it will call OnNavTo() and the other navigation overrides you include.
Best of luck.
While Template10 documentation states the NavigationCacheMode is disabled by default, that isn't the case in it's example templates (as of writing this). This is set in View C# code (.xaml.cs file).
.xaml.cs file
namespace MangaReader.Views {
public sealed partial class MangaDetail : Page {
public MangaDetail() {
InitializeComponent();
//NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled; //this was set by default
NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Disabled;
}
}
}
Now, new ViewModel will be created each time you access a this page.

MvvmCross UITextField custom binding

So I am trying to implement a custom binding for a UITextField in MvvmCross, pretty much along the lines of Binding 'GO' key on Software Keyboard - i.e. trying to bind a text field to automatically fire an event when the Done button is tapped on the keyboard (so binding to ShouldReturn). I also need to bind the text field's EditingDidBegin and EditingDidEnd events. Because I am binding more than one event, I have created a MvxPropertyInfoTargetBinding as follows:
public class MyTextFieldTargetBinding : MvxPropertyInfoTargetBinding<UITextField>
{
private ICommand _command;
protected UITextField TextField
{
get { return (UITextField)Target; }
}
public MyTextFieldTargetBinding(object target, PropertyInfo targetPropertyInfo) : base(target, targetPropertyInfo)
{
TextField.ShouldReturn += HandleShouldReturn;
TextField.EditingDidBegin += HandleEditingDidBegin;
TextField.EditingDidEnd += HandleEditingDidEnd;
}
private bool HandleShouldReturn(UITextField textField)
{
if (_command == null) {
return false;
}
var text = textField.Text;
if (!_command.CanExecute (text)) {
return false;
}
textField.ResignFirstResponder();
_command.Execute(text);
return true;
}
private void HandleEditingDidBegin (object sender, EventArgs e)
{
// do something
}
private void HandleEditingDidEnd (object sender, EventArgs e)
{
// do something
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
public override void SetValue(object value)
{
var command = value as ICommand;
_command = command;
}
public override Type TargetType
{
get { return typeof(ICommand); }
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
if (TextField != null)
{
TextField.ShouldReturn -= HandleShouldReturn;
TextField.EditingDidBegin -= HandleEditingDidBegin;
TextField.EditingDidEnd -= HandleEditingDidEnd;
}
}
base.Dispose(isDisposing);
}
}
My first question is: am I correct in creating one MvxPropertyInfoTargetBinding for all the events? Relatedly, I don't get the difference between MvxPropertyInfoTargetBinding and MvxTargetBinding. According to MVVMCross Binding decimal to UITextField removes decimal point the former is used when replacing an existing binding, the latter for known properties and event pairs. So am I using the correct one?
Secondly (and the real crux of my problem), my code works except for SetValue - it is fired, but the value is null. Here is what I have in my Setup file:
protected override void FillTargetFactories (IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories (registry);
registry.RegisterPropertyInfoBindingFactory(typeof(MyTextFieldTargetBinding), typeof(UITextField), "Text");
}
I don't do anything in my View - perhaps that is where the issue lies?
EDIT:
My ViewModel:
public class LoginViewModel : MvxViewModel
{
private string _username;
public string Username
{
get { return _username; }
set { _username = value; RaisePropertyChanged(() => Username); }
}
private string _password;
public string Password
{
get { return _password; }
set { _password = value; RaisePropertyChanged(() => Password); }
}
private MvxCommand _login;
public ICommand Login
{
get {
_login = _login ?? new MvxCommand(DoLogin);
return _login;
}
}
public LoginViewModel(ILoginManager loginManager)
{
_loginManager = loginManager;
}
private void DoLogin()
{
// call the login web service
}
}
In my `View', I don't do anything fancy (I do create the View elements in a XIB):
public override void ViewDidLoad()
{
base.ViewDidLoad ();
this.NavigationController.SetNavigationBarHidden(true, false);
var set = this.CreateBindingSet<LoginView, Core.ViewModels.LoginViewModel>();
set.Bind(usernameTextField).To(vm => vm.Username);
set.Bind(passwordTextField).To(vm => vm.Password);
set.Bind (loginButton).To (vm => vm.Login);
set.Apply();
}
No interesting Trace messages.
1. What is special about PropertyInfoTargetBinding?
The question you reference - MVVMCross Binding decimal to UITextField removes decimal point - gives the key to the difference between MvxTargetBinding and MvxPropertyInfoTargetBinding:
TargetBinding can be used for any arbitrary binding - e.g. for a non-propertyInfo-based binding
PropertyInfoTargetBinding inherits from TargetBinding and can only be used with actual C# Properties - because it uses PropertyInfo via Reflection.
In your case, since you aren't actually using the Text property via Reflection, then I'd be tempted not to use a PropertyInfoTargetBinding and to steer clear of the Text name as well - instead just write a custom TargetBinding.
the former is used when replacing an existing binding
This is definitely not true - instead any binding can be used to replace another binding - as the answer on the other question says
MvvmCross operates a simple 'last registered wins' system
For more on custom bindings, take a look at:
the N=28 video in http://mvvmcross.blogspot.co.uk/
take a look through some of the "standard" bindings that ship with MvvmCross
Droid - https://github.com/MvvmCross/MvvmCross/tree/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Droid/Target
iOS - https://github.com/MvvmCross/MvvmCross/tree/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Touch/Target
note that most of these use either MvxPropertyInfoTargetBinding or MvxConvertingTargetBinding as a base class
2. Why is my SetValue getting null?
Your current binding code is asking for an ICommand:
public override Type TargetType
{
get { return typeof(ICommand); }
}
But your View code is currently binding the View to a string:
// View
set.Bind(usernameTextField).To(vm => vm.Username);
// ViewModel
private string _username;
public string Username
{
get { return _username; }
set { _username = value; RaisePropertyChanged(() => Username); }
}
To solve this...
Work out what you want to bind to - is it an ICommand (e.g. and MvxCommand) or is it a string?
Change the View and the Binding to reflect this.

Bind "Enabled" properties of controls to a variable

I am running into an issue that I have found on some similar post, however, they are not quite the same and I am not quite sure how to apply it to my scenario. They may or may not be the same as my case. So, I am posting my own question here hopefully, I will get an answer to my specific scenario.
Basically, I have a window form with a bunch of controls. I would like to have the ability to bind their Enabled property to a Boolean variable that I set so that they can be enable or disable to my discretion.
public partial class MyUI : Form
{
private int _myID;
public int myID
{
get
{
return _myID;;
}
set
{
if (value!=null)
{
_bEnable = true;
}
}
}
private bool _bEnable = false;
public bool isEnabled
{
get { return _bEnable; }
set { _bEnable = value; }
}
public myUI()
{
InitializeComponent();
}
public void EnableControls()
{
if (_bEnable)
{
ctl1.Enabled = true;
ctl2.Enabled = true;
......
ctl5.Enabled = true;
}
else
{
ctl1.Enabled = false;
ctl2.Enabled = false;
......
ctl5.Enabled = false;
}
}
}
}
The method EnableControls above would do what I need but it may not be the best approach. I prefer to have ctrl1..5 be bound to my variable _bEnable. The variable will change depending on one field users enter, if the value in the field exists in the database, then other controls will be enabled for user to update otherwise they will be disabled.
I have found a very similar question here
but the data is bound to the text field. How do I get rid of the EnableControls method and bind the value of _bEnabled to the "Enabled" property in each control?
Go look into the MVVM (Model - View - ViewModel) pattern, specifically its implementation within Windows Forms. Its much easier to apply it to a WPF/Silverlight application, but you can still use it with Windows Forms without too much trouble.
To solve your problem directly, you will need to do 2 things:
Create some class that will hold your internal state (i.e. whether or not the buttons are enabled). This class must implement INotifyPropertyChanged. This will be your View Model in the MVVM pattern.
Bind an instance of the class from 1.) above to your Form. Your form is the View in the MVVM pattern.
After you have done 1 and 2 above, you can then change the state of your class (i.e. change a property representing whether a button is enabled from true to false) and the Form will be updated automatically to show this change.
The code below should be enough to get the concept working. You will need to extend it obviously, but it should be enough to get you started.
View Model
public class ViewModel : INotifyPropertyChanged
{
private bool _isDoStuffButtonEnabled;
public bool IsDoStuffButtonEnabled
{
get
{
return _isDoStuffButtonEnabled;
}
set
{
if (_isDoStuffButtonEnabled == value) return;
_isDoStuffButtonEnabled = value;
RaisePropertyChanged("IsDoStuffButtonEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View
public class View : Form
{
public Button DoStuffButton { get; set; }
public void Bind(ViewModel vm)
{
DoStuffButton.DataBindings.Add("Enabled", vm, "IsDoStuffButtonEnabled");
}
}
Usage
public class Startup
{
public ViewModel ViewModel { get; set; }
public View View { get; set; }
public void Startup()
{
ViewModel = new ViewModel();
View = new View();
View.Bind(ViewModel);
View.Show();
ViewModel.IsDoStuffButtonEnabled = true;
// Button becomes enabled on form.
// ... other stuff here.
}
}
Maybe you can try this approach: in your isEnabled property's setter method, add an if statement:
if(_bEnable) EnableControls();
else DisableControls();
And if your control names are ctl1,ctl2... etc. you can try this:
EnableControls()
{
for(int i=1; i<6;i++)
{
string controlName = "ctl" + i;
this.Controls[controlName].Enabled = true;
}
}
And apply the same logic in DisableControls
If you have more controls in future this could be more elegant.

Access to MainViewModel from Bootstrapper

I'm very new to Caliburn Micro and would like to access ViewModel properties during OnExit.
public class AppBootstrapper : Bootstrapper<MainViewModel>
{
protected override void OnExit(object sender, EventArgs e)
{
if (mainViewModel.MyParam == 42)
{
}
base.OnExit(sender, e);
}
From the default WP7 template (without Caliburn) i'm used to have App.ViewModel, which is a static field with a singleton get accessor, where the viewmodel will be created on the first access.
(See next code snippet)
public partial class App : Application
{
private static MainViewModel viewModel = null;
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
set
{
viewModel = value;
}
}
Now i try to use Caliburn Micro 1.1 with a WPF project and don't know how this should be done.
I need access to the ViewModel during OnExit inside AppBootStrapper.
I assume, that this should be possible, because my AppBootstrapper is inherited from Bootstrapper, but can't find the right way of doing this..
Any hints, how this can be done in WPF are very welcome?
Thanks
Rob
Try
MainViewModel mainViewModel = IoC.Get<MainViewModel>();
Here is how it would look in your code:
public class AppBootstrapper : Bootstrapper<MainViewModel>
{
protected override void OnExit(object sender, EventArgs e)
{
// Get the Main View Model
MainViewModel mainViewModel = IoC.Get<MainViewModel>();
if (mainViewModel.MyParam == 42)
{
//Do work
}
base.OnExit(sender, e);
}
}
This assumes Two Things:
Your MainViewModel class is exporting typeof(MainViewModel) and not something different, like typeof(IShell)
You are using the default MEF implementation of C.M.
After searching a little bit more i think i've found the solution to my own question: Added SimpleContainer.cs from here: link
and added this to my AppBootstrapper code:
public class AppBootstrapper : Bootstrapper<MainViewModel>
{
private SimpleContainer container;
protected override void Configure()
{
container = new SimpleContainer();
container.RegisterSingleton(typeof(MainViewModel), null, typeof(MainViewModel));
container.RegisterSingleton(typeof(IWindowManager), null, typeof(WindowManager));
}
protected override object GetInstance(Type service, string key)
{
return container.GetInstance(service, key);
}
Would be great to hear some comment, whether this is ok or not.

Categories