Xamarin Forms and TabGestureRecognizer does not fire with Command - mvvm

I have this problem with Xamarin Forms (Tested on Android and iOS).
I have a simple page
using System;
using Xamarin.Forms;
namespace BugTGR
{
public class PageMain : ContentPage
{
public PageMain()
{
PageMainViewModel vm = new PageMainViewModel();
this.BindingContext = vm;
Label label1 = new Label{ Text = "Press with ICommand"};
TapGestureRecognizer tgr = new TapGestureRecognizer();
tgr.BindingContext = vm;
tgr.SetBinding(TapGestureRecognizer.CommandProperty, "Tapped");
label1.GestureRecognizers.Add(tgr);
Label label2 = new Label { Text = "Press with Tapped"};
TapGestureRecognizer tgr1 = new TapGestureRecognizer();
tgr1.Tapped += async (object sender, EventArgs e) => {
await DisplayAlert("Attention", "PRESSED WITH TAPPED", "Ok");
};
label2.GestureRecognizers.Add(tgr1);
Content = new StackLayout
{
Children = {label1, label2}
};
}
}
}
In this code, I use this ViewModel (very simple, only a command)
using System;
using System.Windows.Input;
using Xamarin.Forms;
using PropertyChanged;
namespace BugTGR
{
[ImplementPropertyChanged]
public class PageMainViewModel
{
public PageMainViewModel()
{
this.Tapped = new Command(async() =>
{
await Application.Current.MainPage.DisplayAlert("Attention", "Pressed", "Ok");
});
}
public ICommand Tapped { protected get; set;}
}
}
Then, how you can see, I try to Bind the Command to a TapGestureRecognizer, then add the TGR to a label, but if I click the label, the command is not called.
In the second label (label2) I add another TapGestureRecognizer without bind the command, using Tapped event. This works!
There is someone that can let me know what am I doing wrong?
Thanks!
Alessandro

Here is working solution. The problem is how you create Tapped command.
Below is 2 ways of doing this. Command or event handler calling VM. If you are doing this in code and not in xaml I would use vm.TappedHandler method
namespace ButtonRendererDemo
{
public class LabelTapPage : ContentPage
{
public LabelTapPage()
{
PageMainViewModel vm = new PageMainViewModel();
this.BindingContext = vm;
Label label1 = new Label { Text = "Press with ICommand" };
TapGestureRecognizer tgr = new TapGestureRecognizer();
label1.GestureRecognizers.Add(tgr);
tgr.SetBinding(TapGestureRecognizer.CommandProperty, "Tapped");
Label label2 = new Label { Text = "Press with Tapped Event" };
TapGestureRecognizer tgr1 = new TapGestureRecognizer();
tgr1.Tapped += async (object sender, EventArgs e) => {
await DisplayAlert("Attention", "Tapped Event: Pressed", "Ok");
};
label2.GestureRecognizers.Add(tgr1);
Label label3 = new Label { Text = "Press with TappedHandler" };
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += async (s, e) => {
await vm.TappedHandler();
};
label3.GestureRecognizers.Add(tapGestureRecognizer);
Content = new StackLayout
{
Children = { label1, label2, label3 }
};
}
}
public class PageMainViewModel : INotifyPropertyChanged
{
ICommand tapCommand;
public PageMainViewModel()
{
tapCommand = new Command(OnTapped);
}
public async Task TappedHandler()
{
await Application.Current.MainPage.DisplayAlert("Attention", "TappedHandler: Pressed", "Ok");
}
public ICommand Tapped
{
get { return tapCommand; }
}
async void OnTapped(object s)
{
await Application.Current.MainPage.DisplayAlert("Attention", "Tapped Command: Pressed", "Ok");
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Also you don't need to set tgr.BindingContext = vm; It is inherited by your page
After I published it I found that there is much easier solution:
Remove "protected" in
public ICommand Tapped { get; set; }
so page can access it. That's it :-)
May be you meant make "set" protected not get?

Related

Binding of Label Text from mongodb retrieved value

I'm having issues binding my label text to the userValue calculated from my mongoDB collection.
I've tried this multiple ways now and would like to simply have this work in the Xamarin's code behind. Please would you provide better guidance than is already out there as current posts on this have not worked...
My XAML:
<Label x:Name="YourLableName"
Text="{Binding UserValue, StringFormat='{0:0}'}"
/>
My CS:
public HomePage()
{
InitializeComponent();
BindingContext = this;
UserData();
}
public async void UserData()
{
var userId = HomePage.userIdentity;
var usersValues = await MongoService.GetUserModel(userId);
foreach (var test in usersValues)
{
userValue = test.usersValueX.ToString();
}
UserValue = userValue;
}
private string _UserValue;
public string UserValue
{
get { return _UserValue; }
set
{
_UserValue = value;
OnPropertyChanged("UserValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
Thank you :)
P.s. I have breakstopped this at UserValue and the value is retrieved in the UserData method however it still does not bind to my label text...
It didn't work because my content page was wrapped in a control template :/ .

Customize the Xamarin.Forms Picker Popup List

I know how to create a custom renderer to customize the actual text of the Xamarin forms picker, but how do you customize, say, the background color or text of the list that pops up when you click on the picker text box?
You can refer to the following code :
in iOS
using System;
using Xamarin.Forms;
using xxx;
using xxx.iOS;
using UIKit;
using Xamarin.Forms.Platform.iOS;
using Foundation;
[assembly:ExportRenderer(typeof(MyPicker), typeof(MyiOSPicker))]
namespace xxx.iOS
{
public class MyiOSPicker:PickerRenderer,IUIPickerViewDelegate
{
IElementController ElementController => Element as IElementController;
public MyiOSPicker()
{
}
[Export("pickerView:viewForRow:forComponent:reusingView:")]
public UIView GetView(UIPickerView pickerView, nint row, nint component, UIView view)
{
UILabel label = new UILabel
{
//here you can set the style of item!!!
TextColor = UIColor.Blue,
Text = Element.Items[(int)row].ToString(),
TextAlignment = UITextAlignment.Center,
};
return label;
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
UIPickerView pickerView = (UIPickerView)Control.InputView;
pickerView.WeakDelegate = this;
pickerView.BackgroundColor = UIColor.Yellow; //set the background color of pickerview
}
}
}
}
in Android
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using xxx;
using xxx.Droid;
using Android.Widget;
using Android.App;
using System.Linq;
[assembly: ExportRenderer(typeof(MyPicker), typeof(MyAndroidPicker))]
namespace xxx.Droid
{
public class MyAndroidPicker:PickerRenderer
{
IElementController ElementController => Element as IElementController;
public MyAndroidPicker()
{
}
private AlertDialog _dialog;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
Control.Click += Control_Click;
}
protected override void Dispose(bool disposing)
{
Control.Click -= Control_Click;
base.Dispose(disposing);
}
private void Control_Click(object sender, EventArgs e)
{
Picker model = Element;
var picker = new NumberPicker(Context);
if (model.Items != null && model.Items.Any())
{
// set style here
picker.MaxValue = model.Items.Count - 1;
picker.MinValue = 0;
picker.SetBackgroundColor(Android.Graphics.Color.Yellow);
picker.SetDisplayedValues(model.Items.ToArray());
picker.WrapSelectorWheel = false;
picker.Value = model.SelectedIndex;
}
var layout = new LinearLayout(Context) { Orientation = Orientation.Vertical };
layout.AddView(picker);
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, true);
var builder = new AlertDialog.Builder(Context);
builder.SetView(layout);
builder.SetTitle(model.Title ?? "");
builder.SetNegativeButton("Cancel ", (s, a) =>
{
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
_dialog = null;
});
builder.SetPositiveButton("Ok ", (s, a) =>
{
ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, picker.Value);
// It is possible for the Content of the Page to be changed on SelectedIndexChanged.
// In this case, the Element & Control will no longer exist.
if (Element != null)
{
if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
Control.Text = model.Items[Element.SelectedIndex];
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is also possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
}
_dialog = null;
});
_dialog = builder.Create();
_dialog.DismissEvent += (ssender, args) =>
{
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
};
_dialog.Show();
}
}
}

set property of new instance

thank you for helping.
First, I created a form with a (user defined) property.
as see below
public partial class nfrmtableitem : Form
{
private DataRow _datarow;
public DataRow U_Table_Row { get { return _datarow; } set { _datarow = value; } }
public nfrmtableitem()
{
InitializeComponent();
}
}
And I create second form with property as type of Form.
as see below
public partial class nftableshow : Form
{
private DataTable _datatable;
public DataTable U_DataTable { get { return _datatable; } set { _datatable = value; } }
private Form _inputform1;
public Form U_DGV_InputForm1 { get { return _inputform1; } set { _inputform1 = value; } }
}
when call it:
any where
nftableshow newfrmtableshow = new nftableshow()
{
Name = "newfrmtableshow",
Text = "Show the table",
MdiParent = this,
U_DGV_InputForm1 = new nfrmtableitem(),
};
newfrmtableshow.Show();
But I can not use the first form property in second form.
and the property is not in instance.
//the button in second form
private void button1_Click_Click(object sender, EventArgs e)
{
Form f1 = _inputform1 as Form;
/*
* {
* U_Table_Row = db.maindataset.Tables["customer"].NewRow(),
* };
*/
f1.Show();
}
Question:
How can I use the First form with specific (user defined) property in second form.
Regards
You should probably use dot notation to access the property of the first form. Try using
//the button in second form
private void button1_Click_Click(object sender, EventArgs e)
{
Form f1 = _inputform1 as Form;
{
f1.U_Table_Row = db.maindataset.Tables["customer"].NewRow(),
};
f1.Show();
}

Delegate command not executing on property change

I am currently trying to make a slider in ViewA change the font size of text in viewA and viewB. I have everything bound correctly, but the delegate command is not calling the execute method when the font size property is changed. If I manually call this function everything works as expected, so it is likely one line of code that is the problem. The ViewAViewModel is below:
public class ViewAViewModel : BindableBase
{
private Person _CoolChick = new Person();
private int _fontSize = 12;
private IEventAggregator _eventAggregator;
public DelegateCommand UpdateSizeCommand { get; set; }
public Person CoolChick
{
get
{
return _CoolChick;
}
set
{
SetProperty(ref _CoolChick, value);
}
}
public int FontSize
{
get { return _fontSize; }
set {
Console.WriteLine(_fontSize + " => Font Size");
SetProperty(ref _fontSize, value);
//Execute();
}
}
public ViewAViewModel(IEventAggregator eventAggregator)
{
CoolChick.Age = 25;
CoolChick.Name = "Methalous";
_eventAggregator = eventAggregator;
//likely the problem in this code
UpdateSizeCommand = new DelegateCommand(Execute, CanExecute).ObservesProperty(() => FontSize);
}
private void Execute()
{
_eventAggregator.GetEvent<UpdateEvent>().Publish(FontSize);
}
private bool CanExecute()
{
return true;
}
}
Why would it? You're not calling UpdateSizeCommand.Execute in the setter of your Font property. The command will not invoke unless you bind it to a command property or invoke it manually.

Trying to Add More tabs to tab panel upon clicking on a button

I would like to add more tabs to the tab panel upon receiving a reponse from a servelet.. the problem is that It only adds the last one and not the the others see part of the code below. It seems like it is only adding the last panel "Time Reports" but not the other two
Thank you
btnLogin.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
if(getLoginResult())
{
HorizontalPanel temp = new HorizontalPanel();
panel.add(temp, "Add Hours");
panel.add(temp, "Time Sheets");
panel.add(temp, "Time Reports");
}
}
});
RootPanel.get().add(panel);
}
private boolean getLoginResult() {
AsyncCallback callback = new AsyncCallback() {
public void onSuccess(Object result) {
isAuthenticated = true;
}
public void onFailure(Throwable caught) {
Window.alert("Error when invoking the pageable data service :" + caught.getMessage());
isAuthenticated = false;
}
};
timesheetLoginServlet.isAuthenticated("1","rapidjava", callback);
return isAuthenticated;
}
}
You can add any widget to its parent only once. Change the temp to temp1, temp2 and temp3