Adding 2 TapGestureRecognizer not possible? - xamarin

I have 2 TapGestureRecognizers on my custom control:
1)
The first one is internal / exists only within the custom control.
2)
The second one is attached on the page on which the custom control is instanciated.
I'm using the first TapGestureRecognizer to trigger an animation at a Tap internally / within the custom control, and the second TapGestureRecognizer is used to track Taps on the custom control on the Page so that I can react to taps.
It feels wrong to do the animation "outside" / on the Page as every instance of this control should animate, that's why I attached a TapGestureRecognizer within the custom control.
However, when I do this, only the "internal" TapGestureRecognizer works, the one on the outside doesn't.
Is that normal behaviour?
public class clsGridCell : ContentView
{
var _Grid1ContainerForImageAndLabel = new Grid()
{
}
var nTapRec = new TapGestureRecognizer();
nTapRec.Tapped += OnItemSelected;
_Grid1ContainerForImageAndLabel.GestureRecognizers.Add(nTapRec);
this.Content = _Grid1ContainerForImageAndLabel;
}
private async void OnItemSelected(object sender, EventArgs e)
{
await Task.WhenAny<bool>
(
_image1.ScaleTo(0.9, 50, Easing.Linear)
);
//run some background color animation, too
}
And "outside" / on the Page:
public class MainPage : ContentPage
{
var nGridCell = new clsGridCell
{
ImageSource = nImgSrc,
BackgroundColor = Color.Blue;
};
_BigGrid.Children.Add(nGridCell);
var nTapRec = new TapGestureRecognizer();
nTapRec.Tapped += OnItemSelected;
nGridCell.GestureRecognizers.Add(nTapRec);
private async void OnItemSelected(object sender, EventArgs e)
{
//Not fired! When I remove the "internal" TapGestureRecognizer, it does work

Just create the internal TapGestureRecognizer as a public/internal property of the class and, instead of creating a new Gesture "outside", add a new Tapped action to the class' TapGestureRecognizer. Like this:
public class clsGridCell : ContentView
{
public TapGestureRecognizer TapGesture { get; set; }
Action<clsGridCell> tap;
public Action<clsGridCell> Tap
{
get => tap;
set
{
tap = value;
TapGesture.Tapped += (sender, e) => { value(this); };
}
}
public clsGridCell()
{
var _Grid1ContainerForImageAndLabel = new Grid() { };
TapGesture = new TapGestureRecognizer();
TapGesture.Tapped += OnItemSelected;
_Grid1ContainerForImageAndLabel.GestureRecognizers.Add(TapGesture);
this.Content = _Grid1ContainerForImageAndLabel;
}
private async void OnItemSelected(object sender, EventArgs e)
{
await Task.WhenAny<bool> ( _image1.ScaleTo(0.9, 50, Easing.Linear) ); //run some background color animation, too
}
}
And outside use myGrid.Tap = Method;

Related

Activity Indicator not working in Xamarin.Forms

I have an activity indicator designed inside a absolute layout. Based on a button click event, I try to show and hide the activity indicator alternatively. But due to some reason, I cannot see my activity Indicator.Any help will be greatly appreciated!!! Thanks in advance.
This is my .xaml.cs class:
public partial class PBTestPage : ContentPage
{
private bool _pbIndicator;
public PBTestPage()
{
InitializeComponent();
}
public bool PBIndicator{
get{
return _pbIndicator;
}set{
_pbIndicator = value;
OnPropertyChanged();
}
}
protected override void OnAppearing()
{
base.OnAppearing();
var parentLayout = new AbsoluteLayout();
var stackContent = new StackLayout();
AbsoluteLayout.SetLayoutFlags(stackContent,AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutBounds(stackContent,new Rectangle(0f,0f,AbsoluteLayout.AutoSize,AbsoluteLayout.AutoSize));
var activityIndicator = new ActivityIndicator
{
Color = Color.Black,
IsRunning = PBIndicator,
IsVisible = PBIndicator
};
AbsoluteLayout.SetLayoutFlags(activityIndicator, AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutBounds(activityIndicator, new Rectangle(.5, .5, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
var button = new Button
{
Text="Click",
VerticalOptions=LayoutOptions.CenterAndExpand,
HorizontalOptions=LayoutOptions.CenterAndExpand,
};
button.Clicked += OnClicked;
stackContent.Children.Add(button);
parentLayout.Children.Add(stackContent);
parentLayout.Children.Add(activityIndicator);
Content = parentLayout;
}
private void OnClicked(object sender, EventArgs e)
{
if(PBIndicator==false){
PBIndicator = true;
}else{
PBIndicator = false;
}
}
}
I'm inferring you're intending to use bindings by the use of OnPropertyChanged, so it's a good time to start do it.
I've made some changes in your code and I guess it will work properly now. The changes are:
Moved the layout creation to the constructor (I can't see create the whole same layout on every time the page is shown as a good choice );
The OnClicked event just invert the value of the property, no need to check it before with an if;
Using Bindings to handle the ActivityIndicator's properties state;
Set true to PBIndicator property on the OnAppearing event.
This is the changed code:
public partial class PBTestPage : ContentPage
{
private bool _pbIndicator;
public bool PBIndicator
{
get { return _pbIndicator; }
set
{
_pbIndicator = value;
OnPropertyChanged();
}
}
public PBTestPage()
{
InitializeComponent();
var parentLayout = new AbsoluteLayout();
var stackContent = new StackLayout();
AbsoluteLayout.SetLayoutFlags(stackContent, AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutBounds(stackContent, new Rectangle(0f, 0f, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
var activityIndicator = new ActivityIndicator
{
Color = Color.Black
};
activityIndicator.SetBinding(ActivityIndicator.IsRunningProperty, new Binding(nameof(PBIndicator)));
activityIndicator.SetBinding(ActivityIndicator.IsVisibleProperty, new Binding(nameof(PBIndicator)));
activityIndicator.BindingContext = this;
AbsoluteLayout.SetLayoutFlags(activityIndicator, AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutBounds(activityIndicator, new Rectangle(.5, .5, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
var button = new Button
{
Text = "Click",
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.CenterAndExpand,
};
button.Clicked += OnClicked;
stackContent.Children.Add(button);
parentLayout.Children.Add(stackContent);
parentLayout.Children.Add(activityIndicator);
Content = parentLayout;
}
protected override void OnAppearing()
{
base.OnAppearing();
PBIndicator = true;
}
private void OnClicked(object sender, EventArgs e)
{
PBIndicator = !PBIndicator;
}
}
Let me know if it works. I hope it helps.
Try this one
private void OnClicked(object sender, EventArgs e)
{
if(PBIndicator==false){
activityIndicator.IsRunning=true;
}else{
activityIndicator.IsRunning=false;
}
}

Receiving event Button in a Grid event from outside

I have created a custom control.
It consists of a Grid in which a Button is placed:
using Xamarin.Forms;
namespace MyApp
{
public class clsButton : ContentView
{
private Grid _grid;
private Button _button;
public clsButton()
{
_grid = new Grid
{
Margin = new Thickness(0),
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
_grid.BindingContext = this;
_button = new Button()
{
};
_button.Clicked += async (sender, e) =>
{
//I tried different things here, but none gave me the right results. I need to "bubble" this click to the outside
return;
};
_grid.Children.Add(_button, 0, 0);
this.Content = _grid;
}
}
}
I create some of these custom controls in a ContentPage like this:
_MyButton = new clsImageButton()
{
};
var nTapGestureRecognizer = new TapGestureRecognizer();
nTapGestureRecognizer.Tapped += OnButtonClicked;
_MyButton.GestureRecognizers.Add(nTapGestureRecognizer);
And this is the void in the same ContentPage:
async void OnButtonClicked(object sender,EventArgs e)
{
//I don't managed to get here
}
This doesn't work.
"OnButtonClicked" is never called.
I think I have to raise an event from within the custom control.
I tried some things, but none of them were successful.
How would I do this correctly?
in your clsButton, declare a public event
public EventHandler ButtonClicked { get; set; }
then raise the event when your button is clicked
_button.Clicked += async (sender, e) =>
{
if (ButtonClicked != null) ButtonClicked(this,e);
};
finally, where ever you are using clsButton, you can subscribe to the event (the gesture recognizer is not needed)
var btn = new clsButton();
btn.ButtonClicked += async (sender, e) => {
// respond here
}

iOS UIMenuController Custom Renderer for Xamarin Forms

I am trying to create a custom renderer so that a context menu is displayed when a user clicks a button. I have it working in Android and UWP but iOS is proving more difficult. When I click the button, everything runs with no errors but the UIMenuController is not displayed, although I cannot click the button again almost as though the view containing the button has overlaid the screen preventing access to the button. I've tried attaching the menu controller to the button, the ContextMenuView.
Here's the custom Xamarin Forms View -
public class ContextMenuView : View
{
public EventHandler MenuRequested;
public void RequestMenu(object sender)
{
if(MenuRequested != null)
{
MenuRequested(sender, EventArgs.Empty);
}
}
}
The ContextMenuView is instantiated from the click event of a button on Main.xaml. Main.xaml consists of an AbsoluteLayout that contains the button being clicked. Here's the click event of the button -
private void ContextMenuButton_Clicked(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Click");
var button = sender as Button;
if (_popupMenu == null)
{
_popupMenu = new ContextMenuView();
Rectangle menuPosition = new Rectangle { X = button.X, Y = button.Y, Width = 50, Height = 50 };
_popupMenu.Layout(((Button)sender).Bounds);
AbsLayout.Children.Add(_popupMenu, menuPosition);
_popupMenu.IsVisible = true;
}
else
{
Rectangle menuPosition = new Rectangle { X = button.X, Y = button.Y + button.Height, Width = 50, Height = 50 };
_popupMenu.Layout(((Button)sender).Bounds);
}
_popupMenu.RequestMenu(sender);
}
And the iOS renderer -
public class ContextMenuViewRendererIOS : ViewRenderer<ContextMenuView, UIView>
{
private UIView _nativeControl;
private ContextMenuView _xamarinControl;
private Xamarin.Forms.AbsoluteLayout _container;
private UIView _iosView;
private nfloat _height;
private nfloat _width;
protected override void OnElementChanged(ElementChangedEventArgs<ContextMenuView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
if (e.NewElement != null)
{
_xamarinControl = e.NewElement;
_xamarinControl.MenuRequested += OnMenuRequested;
}
_height = UIScreen.MainScreen.Bounds.Height;
_width = UIScreen.MainScreen.Bounds.Width;
_nativeControl = new UIView(new CGRect(0, 0, _width, _height));
SetNativeControl(_nativeControl);
}
}
private void OnMenuRequested(object sender, EventArgs e)
{
try
{
var _menu = UIMenuController.SharedMenuController;
BecomeFirstResponder();
var iterm = new UIMenuItem("John", new ObjCRuntime.Selector("MenuItemAction:"));
_menu.MenuItems = new[] { iterm };
_menu.SetTargetRect(new CGRect(10, 10, 100, 100), _nativeControl);
_menu.MenuVisible = true;
}
catch (Exception ex)
{
throw;
}
}
[Export("MenuItemAction:")]
private void MenuItemAction(UIMenuController controller)
{
System.Diagnostics.Debug.WriteLine("MenuItemAction");
}
}
Thanks in advance.
The custom renderer needs to override CanBecomeFirstResponder and CanPerform(Selector action, NSObject withSender) and return true from both.

Expanding the size of ListView cell on tap

I'm trying to implement a solution to increase the size of a ListView Cell when tapped using Xamarin Forms (and custom renderers if required).
I'm still pretty new to C#, and the idea of data binding is still a little unclear to me, however, it seems like that is the way to go to solve this problem (perhaps something along the lines of binding the Height / HeightRequest properties of the cell?).
My attempts thus far have been unsuccessful.
If anyone could give me a push in the right direction it would be much appreciated.
Thank you!
ViewCell does not expose Height as a BindableProperty in Xamarin.Forms 1.4.2x
However if you create your own BindableProperty in your Model you can achieve changing the height still as shown below:-
Model:-
public class MenuItem2 : BindableObject
{
public static readonly BindableProperty TextProperty = BindableProperty.Create<MenuItem2, string>(p => p.Text, default(string));
public static readonly BindableProperty CellHeightProperty = BindableProperty.Create<MenuItem2, int>(p => p.CellHeight, default(int));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public int CellHeight
{
get { return (int)GetValue(CellHeightProperty); }
set { SetValue(CellHeightProperty, value); }
}
}
XAML:-
<StackLayout>
<Button x:Name="cmdButton1" Text="Change Cell Heights" Clicked="cmdButton1_Clicked"/>
<ListView x:Name="lstItems" />
</StackLayout>
XAML Code-Behind:-
lstItems.HasUnevenRows = true;
lstItems.ItemTemplate = new DataTemplate(typeof(Classes.MenuCell2));
//
lstItems.ItemsSource = new List<MenuItem2>
{
new MenuItem2(),
new MenuItem2(),
new MenuItem2(),
new MenuItem2(),
};
If you don't set .HasUnevenRows you will not be able to change the cell height.
void cmdButton1_Clicked(object sender, EventArgs e)
{
Random objRandom = new Random();
//
var objItems = lstItems.ItemsSource;
//
foreach (MenuItem2 objMenuItem in objItems)
{
int intNewCellHeight = objRandom.Next(80, 160);
objMenuItem.CellHeight = intNewCellHeight;
objMenuItem.Text = "Cell Height = " + intNewCellHeight.ToString();
}
}
Custom ViewCell:-
public class MenuCell2 : ViewCell
{
public MenuCell2()
{
Label objLabel = new Label
{
YAlign = TextAlignment.Center,
TextColor = Color.Yellow,
};
objLabel.SetBinding(Label.TextProperty, new Binding("Text"));
StackLayout objLayout = new StackLayout
{
Padding = new Thickness(20, 0, 0, 0),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.StartAndExpand,
Children = { objLabel }
};
Frame objFrame_Inner = new Frame
{
Padding = new Thickness(15, 15, 15, 15),
HeightRequest = 36,
OutlineColor = Color.Accent,
BackgroundColor = Color.Blue,
Content = objLayout,
};
Frame objFrame_Outer = new Frame
{
Padding = new Thickness(0, 0, 0, 10),
Content = objFrame_Inner
};
View = objFrame_Outer;
this.BindingContextChanged += MenuCell2_BindingContextChanged;
}
void MenuCell2_BindingContextChanged(object sender, EventArgs e)
{
MenuItem2 objMenuItem = (MenuItem2)this.BindingContext;
objMenuItem.PropertyChanged += objMenuItem_PropertyChanged;
}
void objMenuItem_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "CellHeight":
this.Height = (this.BindingContext as MenuItem2).CellHeight;
(this.View as Frame).ForceLayout();
break;
}
}
Remember to call ForceLayout on the root element of the ViewCell's View property, so it can redraw correctly.
This will give you a result something similar to the following (tested only on WindowsPhone at present):-
In order to do it on a ViewCell being tapped, on the XAML Page add:-
lstItems.ItemTapped += lstItems_ItemTapped;
and then change the model for the item to something like this:-
void lstItems_ItemTapped(object sender, ItemTappedEventArgs e)
{
(e.Item as MenuItem2).CellHeight = 200;
}
Xamarin now has an official example of doing this right within xaml and xaml code behind:
Overview:
https://developer.xamarin.com/samples/xamarin-forms/UserInterface/ListView/DynamicUnevenListCells/
Code:
https://github.com/xamarin/xamarin-forms-samples/tree/master/UserInterface/ListView/DynamicUnevenListCells

How to properly use datacontext multiple ways

OK, so I wanted to use a color picker and found Alex Yakhnin's blog...
http://blogs.msdn.com/b/priozersk/archive/2010/09/17/customizing-picker-box-dialog.aspx
after implementing this code from the blog:
DialogViewModel viewModel;
PickerBoxDialog customDialog;
ColorItem currentColorItem;
private void InitCustomPickerDialog()
{
// Initialize viewmodel
this.viewModel = new DialogViewModel();
this.currentColorItem = viewModel.Items[0];
// Assing it to the page's DataContext
this.DataContext = currentColorItem;
this.customDialog = new PickerBoxDialog();
this.customDialog.Title = "ACCENTS";
// Assign our style to the dialog
this.customDialog.Style = this.Resources["Custom"] as Style;
this.customDialog.ItemSource = viewModel.Items;
this.customDialog.Closed += new EventHandler(customDialog_Closed);
}
void customDialog_Closed(object sender, EventArgs e)
{
this.currentColorItem = (ColorItem)this.customDialog.SelectedItem;
this.DataContext = currentColorItem;
}
private void buttonColor_Click(object sender, RoutedEventArgs e)
{
this.customDialog.Show();
}
I realized that the page's datacontext is being used to set the colors for the picker. I am using a listbox on this same page which also sets the datacontext of the page to display a list of fish.
public FishsPage()
{
InitializeComponent();
DataContext = App.vmFish;
InitCustomPickerDialog();
}
Therefore, I need the page's datacontext for 2 different things now. Is there a way to use the color picker control and the fish list simultaneously?
Erno's suggestion?:
public class FishViewModelComplete : INotifyPropertyChanged
{
private readonly ReefServiceClient wcfProxy;
public FishViewModelComplete()
{
vmFish = new FishViewModel();
vmDialog = new DialogViewModel();
}
private FishViewModel _vmFish;
public FishViewModel vmFish
{
get
{
return _vmFish;
}
set
{
_vmFish = value;
}
}
private DialogViewModel _vmDialog;
public DialogViewModel vmDialog
{
get
{
return _vmDialog;
}
set
{
_vmDialog = value;
}
}
}
Create a third ViewModel that exposes the two ViewModels through properties and bind the appropriate controls to these.

Resources