Move control freely inside AbsoluteLayout in .Net Maui - xamarin.forms

I'm trying to make a layout that allow me to move controls inside it freely, I found a working solution but it has a very strange behavior, when I try to move the label, the movement is very laggy and sometimis it has an effect like it duplicate the label.
I implemented the movement with a PanGestureRecognizer adding labels inside an AbsoluteLayout programatically with a button event
This is the XAML, with the empty AbsoluteLayout and the button at the end to add de label
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Views.MoveControlsView"
Title="MoveControlsView">
<StackLayout>
<AbsoluteLayout
x:Name="ParentLayout"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
</AbsoluteLayout>
<StackLayout
HorizontalOptions="FillAndExpand"
VerticalOptions="End">
<Button
x:Name="AddLabel"
Text="Add label"
Clicked="AddLabel_Clicked"/>
</StackLayout>
</StackLayout>
</ContentPage>
This is the code behind, I generate a Label when the button is clicked and add to it the PanGestureRecognizer that I also suscribed it to the PanUpdated event.
public partial class MoveControlsView : ContentPage
{
public MoveControlsView()
{
InitializeComponent();
}
private void AddLabel_Clicked(object sender, EventArgs e)
{
var label = new Label()
{
Text = "This is a label",
BackgroundColor = Colors.LightGray,
Padding = 10
};
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += PanGestureRecognizer_PanUpdated;
label.GestureRecognizers.Add(panGesture);
ParentLayout.Children.Add(label);
}
private void PanGestureRecognizer_PanUpdated(object sender, PanUpdatedEventArgs e)
{
var label = sender as Label;
switch (e.StatusType)
{
case GestureStatus.Running:
label.TranslationX = e.TotalX;
label.TranslationY = e.TotalY;
break;
case GestureStatus.Completed:
label.TranslateTo(label.TranslationX, label.TranslationY);
break;
}
}
}

You could make changes to the code in PanGestureRecognizer_PanUpdated event handler. Try the following code:
double tempx = 0;
double tempy = 0;
private void PanGestureRecognizer_PanUpdated(object sender, PanUpdatedEventArgs e)
{
var label = sender as Label;
switch (e.StatusType)
{
case GestureStatus.Started:
if(Device.RuntimePlatform == Device.iOS)
{
tempx = label.TranslationX;
tempy = label.TranslationY;
}
break;
case GestureStatus.Running:
if (Device.RuntimePlatform == Device.iOS)
{
label.TranslationX = e.TotalX + tempx;
label.TranslationY = e.TotalY + tempy;
}
else if (Device.RuntimePlatform == Device.Android)
{
label.TranslationX += e.TotalX;
label.TranslationY += e.TotalY;
}
break;
case GestureStatus.Completed:
tempx = label.TranslationX;
tempy = label.TranslationY;
break;
}
}
For more information, you could refer to Xamarin.Forms AbsoluteLayout and Add a pan gesture recognizer
Hope it works for you.

Related

Xamarin Boolean Bindable Property

I am trying to make clickable Icon which will be using for Wish List, for this I have created boolean property which will return Image.
This is my code, but it does not support onClick event, Please advise to figure out this problem.
public class WishIconImg : Image, IDisposable
{
static FontImageSource unselected_source = new FontImageSource();
static FontImageSource selected_source = new FontImageSource();
public WishIconImg()
{
unselected_source.FontFamily = "FA-S";
unselected_source.Glyph = "\U000f02d5";
unselected_source.Color = Color.DarkOrange;
selected_source.FontFamily = "FA-S";
selected_source.Glyph = "\U000f02d1";
selected_source.Color = Color.DarkOrange;
OnClick += Checkbox_OnClick;
}
public static BindableProperty IsCheckedProperty = BindableProperty.Create(
nameof(IsChecked), typeof(bool), typeof(WishIconImg), defaultBindingMode: BindingMode.TwoWay,
propertyChanged: IsCheckedChanged);
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
private static void IsCheckedChanged(BindableObject bindable, object oldValue, object newValue)
{
var cb = (WishIconImg)bindable;
if (cb == null)
return;
if ((bool)newValue)
{
cb.Source = selected_source;
}
else
{
cb.Source=unselected_source ;
}
}
void Checkbox_OnClick(object sender, EventArgs e)
{
IsChecked = !IsChecked;
}
public void Dispose()
{
OnClick -= Checkbox_OnClick;
}
}
}
Xaml
<controls:WishIconImg x:Name="HeartChk" IsChecked="{Binding AddWish, Mode=TwoWay}" HeightRequest="35" WidthRequest="35" HorizontalOptions="End"/>
Even I have tried with Label property but it doesnt work
You could modify the class like following
public WishIconImg()
{
unselected_source.FontFamily = "FA-S";
unselected_source.Glyph = "\U000f02d5";
unselected_source.Color = Color.DarkOrange;
selected_source.FontFamily = "FA-S";
selected_source.Glyph = "\U000f02d1";
selected_source.Color = Color.DarkOrange;
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += (s, e) => {
// handle the tap
IsChecked = !IsChecked;
};
this.GestureRecognizers.Add(tapGestureRecognizer);
}
Try adding TapGestureRecognizer for an click event
Do something like this
<StackLayout HeightRequest="35" WidthRequest="35" HorizontalOptions="End">
<controls:WishIconImg x:Name="HeartChk" IsChecked="{Binding AddWish, Mode=TwoWay}" />
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Checkbox_OnClick}" />
</StackLayout.GestureRecognizers>
</StackLayout>
In your ViewModel Bind the command for it
public System.Windows.Input.ICommand Checkbox_OnClick => new Xamarin.Forms.Command(Checkbox_OnClickTapped);
Checkbox_OnClickTapped will be your method called when your view will be clicked

Flexlayout does not work with command and command parameter

I am being trying to use the flexlayout and it's great but now for each item in the stack layout i need to add tapped gesturer,command etc..
Whatever I try does not work.The only thing that works is the stacklayout.TapGestureRecognizer but as soon as I try to use the command does not work.
I even tried the https://taubensee.net/adding-touch-events-to-flexlayouts/
and added a commandparameter bindable property but does not work either.
How do you add a command with commandparameter to flexlayout .below is my code
<FlexLayout BindableLayout.ItemsSource="{Binding Customers}"
AlignContent="Start"
AlignItems="Start"
Direction="Row"
JustifyContent="Start"
Wrap="Wrap">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout
FlexLayout.AlignSelf="Start"
FlexLayout.Basis="50%">
<!--<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
</StackLayout.GestureRecognizers>-->
<Frame>
<!--<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding ItemTappedCommand}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>-->
<StackLayout>
<Label Text="whatever"></Label>
<!--<Image Source="myimage.png">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ItemTappedCommand}" CommandParameter="AAAA"
NumberOfTapsRequired="1"></TapGestureRecognizer>
</Image.GestureRecognizers>
</Image>-->
</StackLayout>
</Frame>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
<!--<FlexLayout.Behaviors>
<behaviors:FlexLayoutItemTappedBehavior
Command="{Binding ItemTappedCommand2}" CommandParameter="{Binding .}"/>
</FlexLayout.Behaviors>-->
</FlexLayout>
FlexLayout maybe miss the touch events and commands , have a try with Behavior to realize it.
Create a new class that inherits from Behavior<T>:
public class FlexLayoutItemTappedBehavior : Behavior<FlexLayout>
{
public static readonly BindableProperty CommandProperty =
BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(FlexLayoutItemTappedBehavior), defaultBindingMode: BindingMode.OneWay);
public static readonly BindableProperty ParamaterProperty =
BindableProperty.Create(nameof(Paramater), typeof(object), typeof(FlexLayoutItemTappedBehavior), defaultBindingMode: BindingMode.OneWay);
public ICommand Command
{
get => (ICommand)this.GetValue(CommandProperty);
set => this.SetValue(CommandProperty, value);
}
public object Paramater
{
get => (object)this.GetValue(ParamaterProperty);
set => this.SetValue(ParamaterProperty, value);
}
protected override void OnAttachedTo(FlexLayout bindable)
{
base.OnAttachedTo(bindable);
if (bindable.BindingContext != null)
{
this.BindingContext = bindable.BindingContext;
}
bindable.BindingContextChanged += this.OnFlexLayoutBindingChanged;
bindable.ChildAdded += this.OnFlexLayoutChildAdded;
}
protected override void OnDetachingFrom(FlexLayout bindable)
{
base.OnDetachingFrom(bindable);
bindable.BindingContextChanged -= this.OnFlexLayoutBindingChanged;
bindable.ChildAdded -= this.OnFlexLayoutChildAdded;
foreach (var child in bindable.Children)
{
if (child is View childView && childView.GestureRecognizers.Any())
{
var tappedGestureRecognizers = childView.GestureRecognizers.Where(x => x is TapGestureRecognizer).Cast<TapGestureRecognizer>();
foreach (var tapGestureRecognizer in tappedGestureRecognizers)
{
tapGestureRecognizer.Tapped -= this.OnItemTapped;
childView.GestureRecognizers.Remove(tapGestureRecognizer);
}
}
}
}
private void OnFlexLayoutBindingChanged(object sender, EventArgs e)
{
if (sender is FlexLayout flexLayout)
{
this.BindingContext = flexLayout.BindingContext;
}
}
private void OnFlexLayoutChildAdded(object sender, ElementEventArgs args)
{
if (args.Element is View view)
{
var tappedGestureRecognizer = new TapGestureRecognizer();
tappedGestureRecognizer.Tapped += this.OnItemTapped;
view.GestureRecognizers.Add(tappedGestureRecognizer);
}
}
private async void OnItemTapped(object sender, EventArgs e)
{
if (sender is VisualElement visualElement)
{
var animations = new List<AnimationBase>();
var scaleIn = new ScaleToAnimation
{
Target = visualElement,
Scale = .95,
Duration = "50"
};
animations.Add(scaleIn);
var scaleOut = new ScaleToAnimation
{
Target = visualElement,
Scale = 1,
Duration = "50"
};
animations.Add(scaleOut);
var storyBoard = new StoryBoard(animations);
await storyBoard.Begin();
}
if (sender is BindableObject bindable && this.Command != null && this.Command.CanExecute(null))
{
object resolvedParameter;
if (Paramater != null)
{
resolvedParameter = Paramater;
}
else
{
resolvedParameter = e;
}
if (Command.CanExecute(resolvedParameter))
{
this.Command.Execute(bindable.BindingContext);
}
}
}
}
Lastly, in order to use this Behavior from XAML, you can reference it like this:
<FlexLayout.Behaviors>
<behaviors:FlexLayoutItemTappedBehavior
Command="{Binding NavigateToDetailCommand}" Paramater="{Binding .}"/>
</FlexLayout.Behaviors>
About Reusable EventToCommandBehavior , you can refer to here .

Can't create content with DataTemplate in code behind with control defined in xaml

I have the following label in a ResourceDictionary in xaml of my ContentPage:
<ContentPage.Resources>
<ResourceDictionary>
<Label Text="I am label" x:Name="label" x:Key="label"/>
</ResourceDictionary>
</ContentPaget.Resources>
And in in my code behind I have this clicked event handler:
void Handle_Clicked(object sender, System.EventArgs e)
{
DataTemplate dataTemplate = new DataTemplate(() => label);
for (int i = 0; i < 3; i ++)
{
Label content = (Label) dataTemplate.CreateContent();
stack.Children.Add(content);
}
}
In my StackLayout called stack - only 1 label is added when the button assigned with Handle_Clicked is pressed. Why is only 1 label added - when there should be 3 labels added?
I suspect that all controls need a unique id. Since this was not working either:
void Handle_Clicked(object sender, System.EventArgs e)
{
for (int i = 0; i < 3; i ++)
{
stack.Children.Add(label);
}
}
which had brought me to try and use DataTemplate in the first place. Meaning the same object can only be added once to the view.
It can also be noted that createContent() works - but only if it is being defined in xaml (not being instantiated in code behind):
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Name="dataTemplate" x:Key="dataTemplate">
<Label Text="I am label"/>
</DataTemplate>
</ResourceDictionary>
</ContentPaget.Resources>
The workaround I found was to get rid of the DataTemplate and clone the object instead before adding it:
void Handle_Clicked(object sender, System.EventArgs e)
{
for (int i = 0; i < 3; i ++)
{
var l = FastDeepCloner.DeepCloner.Clone(label);
stack.Children.Add(l);
}
}

Xamarin Forms custom stepper

I am trying to make a custom stepper to use in my listview such as this one
Any idea how to do this? Thanks.
Solution 1:
A Stepper allows inputting a discrete value that is constrained to a range. You could display the value of the Stepper using data binding in a label as follows :
Define in XAML:
<StackLayout x:Name="Container">
<Label BindingContext="{x:Reference stepper}" Text="{Binding Value}" />
<Stepper Minimum="0" Maximum="10" x:Name="stepper" Increment="0.5" />
</StackLayout>
Solution 2:
You could create a BindableProperty to implement this function, for example:
public class CustomStepper : StackLayout
{
Button PlusBtn;
Button MinusBtn;
Entry Entry;
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
propertyName: "Text",
returnType: typeof(int),
declaringType: typeof(CustomStepper),
defaultValue: 1,
defaultBindingMode: BindingMode.TwoWay);
public int Text
{
get { return (int)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public CustomStepper()
{
PlusBtn = new Button { Text = "+", WidthRequest = 40, FontAttributes = FontAttributes.Bold, FontSize = 15 };
MinusBtn = new Button { Text = "-", WidthRequest = 40, FontAttributes = FontAttributes.Bold, FontSize = 15 };
switch (Device.RuntimePlatform)
{
case Device.UWP:
case Device.Android:
{
PlusBtn.BackgroundColor = Color.Transparent;
MinusBtn.BackgroundColor = Color.Transparent;
break;
}
case Device.iOS:
{
PlusBtn.BackgroundColor = Color.DarkGray;
MinusBtn.BackgroundColor = Color.DarkGray;
break;
}
}
Orientation = StackOrientation.Horizontal;
PlusBtn.Clicked += PlusBtn_Clicked;
MinusBtn.Clicked += MinusBtn_Clicked;
Entry = new Entry
{
PlaceholderColor = Color.Gray,
Keyboard = Keyboard.Numeric,
WidthRequest = 40, BackgroundColor = Color.FromHex("#3FFF")
};
Entry.SetBinding(Entry.TextProperty, new Binding(nameof(Text), BindingMode.TwoWay, source: this));
Entry.TextChanged += Entry_TextChanged;
Children.Add(PlusBtn);
Children.Add(Entry);
Children.Add(MinusBtn);
}
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
if (!string.IsNullOrEmpty(e.NewTextValue))
this.Text = int.Parse(e.NewTextValue);
}
private void MinusBtn_Clicked(object sender, EventArgs e)
{
if (Text > 1)
Text--;
}
private void PlusBtn_Clicked(object sender, EventArgs e)
{
Text++;
}
}
For more detailed information, please refer to the following documents:
Stepper in Xamarin Forms
Stepper Control In Xamarin.Forms Application For Android And UWP
C# (CSharp) Xamarin.Forms.Stepper Code Examples
Xamarin Forms Guide -- Stepper
Update:
In the CustomStepper class, the Entry value is binding with the Text property, so you could get the value of the entry via customStepper.Text.
For example:
<local:CustomStepper x:Name="MyCustomerStepper"/>
You could get its Entry value in your xaml.cs file via:
var yourCustomerStepperEntryValue = MyCustomerStepper.Text.ToString();

How to change DetailPage Data when Masterpage ListView Item is selected Xamarin forms

I have created a master-Detail Page in Xamarin Forms. I have created only one DetailPage for each items of MasterPage. So, when i click MasterPage Items only data should change not the Layout/Contents of the DetailPage. Please help me.
MasterPage:
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ProductSelctionScreen : MasterDetailPage
{
public string customer;
public ProductSelctionScreen(string Customer)
{
InitializeComponent();
customer = Customer;
DetailPage.Choosedcustomer.Text = customer;
MasterPage.ListView.ItemSelected += ListView_ItemSelected;
}
public void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as ProductSelctionScreenMenuItem;
if (item == null)
return;
var page = (Page)Activator.CreateInstance(item.TargetType);
page.Title = item.Title;
switch (page.Title)
{
case "Foods":
if (DetailPage.productListView.ItemsSource != null)
{
DetailPage.productListView.ItemsSource = null;
BindingContext = new ProductItemModel1();
DetailPage.productListView.SetBinding(ListView.ItemsSourceProperty, new Binding("ProductList"));
DetailPage.productListView.SelectedItem = null;
}
break;
case "Beverages":
break;
case "Personal Care":
break;
case "Health Supplements":
break;
case "Cosmetics":
break;
case "Ayurveda Medicines":
break;
}
Detail = new NavigationPage(page);
IsPresented = false;
MasterPage.ListView.SelectedItem = null;
}
}
On Clicking an item in masterPage I want to change the ItemSource of DetailPage Listview(below)
DetailPage Xamlfile :
<ListView x:Name="ProductListView" ItemsSource="{Binding BeverageList}" HasUnevenRows="True" SeparatorVisibility="None" >
<ListView.ItemTemplate>
<DataTemplate>
<local:Productdata/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Resources