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 .
Related
Please i need some one to help me on how to change player icon into my player page in xamarin forms
This is my code:
PlayerPage.xaml
<StackLayout>
<Button x:Name="btnpause"
WidthRequest="20"
HeightRequest="20"
HorizontalOptions="Center"
ImageSource="pause.png" />
<Button x:Name="btnPlay"
WidthRequest="20"
HeightRequest="20"
HorizontalOptions="Center"
ImageSource="play.png" />
<Switch x:Name="switchLoop" IsToggled="False" />
</StackLayout>
PlayerPage.xaml.cs
ISimpleAudioPlayer player;
public PlayerPage()
{
InitializeComponent();
var player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current;
player.Load("music.mp3");
btnPlay.Clicked += BtnPlayClicked;
switchLoop.Toggled += SwitchLoopToggled;
}
private void SwitchLoopToggled(object sender, ToggledEventArgs e)
{
Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current.Loop = switchLoop.IsToggled;
}
private void BtnPlayClicked(object sender, EventArgs e)
{
Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current.Play();
}
Thank you.
Take a look at PlaybackEnded , it is raised when audio playback completes successfully .
You can control the button's visibility in the event .
Code example
var player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current;
player.PlaybackEnded += (sender,e)=>
{
btnPlay.IsVisible = true;
btnpause.IsVisible = false;
};
I think you mean like this ,did not added the image .Use a ImageButton instead.
On start the button play and the loop switch , when the switch is True the sounds repeats it self. When start the pause button is showing push it the Play is showing again to resume. The example project is on Github https://github.com/borisoprit/MusicStackOverflow
<StackLayout>
<ImageButton
x:Name="btnPlay"
HeightRequest="50"
HorizontalOptions="Center"
Source="Play.png"
VerticalOptions="Center"
WidthRequest="50" />
<ImageButton
x:Name="btnPause"
HeightRequest="50"
HorizontalOptions="Center"
Source="pause.png"
VerticalOptions="Center"
WidthRequest="50"
IsVisible="false"/>
<Label
HorizontalOptions="Start"
Text="Loop:"
VerticalOptions="Center" />
<Switch
x:Name="switchLoop"
HorizontalOptions="Start"
IsToggled="False"
VerticalOptions="Center" />
</StackLayout>
PlayerPage.xaml.cs i also place the OnAppearing. This is the state of the btnpause Isvisible = false .Music ended the Play button is there and pause is hiding again.
public HeadProjectAudioPage()
{
InitializeComponent();
btnPause.IsVisible = false;
var player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current;
player.PlaybackEnded += (sender, e) =>
{
btnPlay.IsVisible = true;
btnPause.IsVisible = false;
};
player.Load("running.mp3");
btnPlay.Clicked += BtnPlayClicked;
btnPause.Clicked += BtnPauseClicked;
switchLoop.Toggled += SwitchLoopToggled;
}
protected override void OnAppearing()
{
base.OnAppearing();
btnPause.IsVisible = false;
}
private void SwitchLoopToggled(object sender, ToggledEventArgs e)
{
Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current.Loop = switchLoop.IsToggled;
}
private void BtnPlayClicked(object sender, EventArgs e)
{
btnPause.IsVisible = true;
btnPlay.IsVisible = false;
Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current.Play();
}
private void BtnPauseClicked(object sender, EventArgs e)
{
btnPlay.IsVisible = true;
btnPause.IsVisible = false;
Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current.Pause();
}
}
In my page I use a Listview, which is contains Label;
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
........
........
........
<Label Text="{Binding .}"
TextColor="{DynamicResource Key=textColor}"
VerticalOptions="StartAndExpand"
HorizontalOptions="StartAndExpand"
Grid.Row="1">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
</Label.GestureRecognizers>
</Label>
........
........
........
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
I can Change text of Label color with "TapGestureRecognizer_Tapped" metod, which i want;
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
...
...
...
var entity = ((Label)sender);
entity.BackgroundColor = Color.FromHex("54C642");
entity.TextColor = Color.FromHex("FFFFFF"); ;
...
...
...
}
And i save color informations to sqlite database.
Example Data:
ID:01 ||
Colors:1/54C642,2/EA4B3E,3/54C642,4/D8E330.... (As many as the list view item count)
When page is open, i want to Labels color apply with databese info.
(I can change the Listview items colors and save them to the database of the color information. My main problem is; when opening page load the list with the color information (As in the picture))
IMAGE
How can i do?
Thanks.
UPDATE
Here is the problem image: IMAGE
For Label text my data like this ",item1,item2,item3,item4,item5,item6,item7,item8", for backgroundcolor is ",726E6E,EA4B3E,D8E330,54C642,726E6E,726E6E,726E6E,D8E330".
And so i used two list for itemsource.
XAML
<Label Text="{Binding .}"
BackgroundColor="{Binding ., Converter={StaticResource colorconverter}}"
TextColor="{DynamicResource Key=textColor}"
VerticalOptions="StartAndExpand"
HorizontalOptions="StartAndExpand"
Grid.Row="1">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
</Label.GestureRecognizers>
</Label>
CS
private async void LstFill()
{
App.Current.Resources["textColor"] = Color.FromHex("FFFFFF");
lstNks.Clear();
lstColors.Clear();
string strNks = "";
string strColors = "";
_sqLiteConnection = await DependencyService.Get<ISQLite>().GetConnection();
var listData = _sqLiteConnection.Table<TBL>().ToList();
strNoksanlar = listData[index].T1NKS;
strRenkler = listData[index].COLOR;
char[] chr = { ',' };
if (strNoksanlar != "")
{
string[] pNks = strNks.Split(chr);
string[] pColors = strColors.Split(chr);
for (int i = 0; i < pNks.Length; i++)
{
if (pNks[i] != "")
{
lstNks.Add(i + ". " + pNks[i]);
}
}
for (int i = 0; i < pColors.Length; i++)
{
if (pColors[i] != "")
{
lstColors.Add(pColors[i]);
}
}
}
lst1Teftis.ItemsSource = null;
lst1Teftis.ItemsSource = lstNks;
lst1Teftis.ItemsSource = lstColors;
}
And i save color informations to sqlite database.Example Data: ID:01
|| Colors:1/54C642,2/EA4B3E,3/54C642,4/D8E330.... (As many as the list
view item count). When page is open, i want to Labels color apply with
databese info.
According to your description, you want to save ListView's Label TextColor in database, I suggest you can create new class that contains two or more string to save ListView's items TextColor, then using DataBinding to display.
I create simple sample using sqlite database that you can take a look.
There are two string properties, one is Label text, another is TextColor.
public class colorclass
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string name { get; set; }
public string color { get; set; }
}
Want to bind color property to Label's TextColor, need to use Xamarin.Forms Binding Value Converters, to convert string to Xamarin.Forms.Color.
<ContentPage.Resources>
<converter:converter1 x:Key="colorconverter" />
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<ListView x:Name="listview1">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label
BackgroundColor="Red"
Text="{Binding name}"
TextColor="{Binding color, Converter={StaticResource colorconverter}}"
VerticalOptions="CenterAndExpand">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button
x:Name="btninsert"
Clicked="btninsert_Clicked"
Text=" insert data" />
</StackLayout>
</ContentPage.Content>
public class converter1 : IValueConverter
{
Color c;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value!=null)
{
c =Color.FromHex((string)value);
}
return c;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Getting ListView current item by (Label)sender).BindingContext
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
var entity = ((Label)sender);
colorclass currentitem = (colorclass)((Label)sender).BindingContext;
currentitem.color = "54C642";
conn.Update(currentitem);
}
Update:
Save listview item color in database, then refresh listview data on contentpage's OnAppearing.
public partial class Page3 : ContentPage
{
public SQLiteConnection conn;
public Page3()
{
InitializeComponent();
conn = GetSQLiteConnection();
// conn.CreateTable<colorclass>();
}
public SQLiteConnection GetSQLiteConnection()
{
var fileName = "colors.db";
var documentPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData);
var path = Path.Combine(documentPath, fileName);
var connection = new SQLiteConnection(path);
return connection;
}
private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
var entity = ((Label)sender);
colorclass currentitem = (colorclass)((Label)sender).BindingContext;
currentitem.color = "54C642";
conn.Update(currentitem);
}
protected override void OnAppearing()
{
base.OnAppearing();
listview1.ItemsSource = conn.Table<colorclass>().ToList();
}
}
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
I have 15 options on my home page. Initially, I will show 9 options in UI. To view the remaining 6 icons, the user slides to the right and back to the left to see previous. I try to implement the swiping feature like below, but it is not working.
XAML
<StackLayout x:Name="firstLlayout">
<Grid>
//3 icons in horizontal
</Grid>
<Grid>
//3 icons in horizontal
</Grid>
<Grid>
//3 icons in horizontal
</Grid>
<StackLayout.GestureRecognizers>
<SwipeGestureRecognizer Direction="Right" Swiped="RightSwipe"/>
</StackLayout.GestureRecognizers>
</StackLayout>
<StackLayout IsVisible="False" x:Name="secondLayout">
<Grid>
//3 icons in horizontal
</Grid>
<Grid>
//3 icons in horizontal
</Grid>
<StackLayout.GestureRecognizers>
<SwipeGestureRecognizer Direction="Left" Swiped="LeftSwipe"/>
</StackLayout.GestureRecognizers>
</StackLayout>
XAML.CS
public void RightSwipe(object sender, EventArgs e)
{
firstLlayout.IsVisible = false;
secondLayout.IsVisible = true;
}
public void LeftSwipe(object sender, EventArgs e)
{
secondLayout.IsVisible = false;
firstLlayout.IsVisible = true;
}
When try left and right swipe nothing is happening in UI, and code execution not coming to event functions. What I am missing here?
Cause1:
Swipe action will conflict with scroll action if you put the stacklayout in a ScrollView .
Solution:
Remove the ScrollView from Root StackLayout, then the swiping will work.
Cause2: It is necessary to add a child control(like Image or Label)to StackLayout , otherwise the swipe action will never been called .
Solution: If you do want to let the content of StackLayout shows nothing in default, you can check the following code .
in code behind
using System;
using Xamarin.Forms;
namespace xxx
{
public class GestureScrollView : ScrollView
{
public event EventHandler SwipeLeft;
public event EventHandler SwipeRight;
public void OnSwipeLeft() =>
SwipeLeft?.Invoke(this, null);
public void OnSwipeRight() =>
SwipeRight?.Invoke(this, null);
}
}
in Android Project
using System;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using xxx;
using xxx.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(GestureScrollView), typeof(GestureScrollViewRenderer))]
namespace xxx.Droid
{
public class GestureScrollViewRenderer : ScrollViewRenderer
{
readonly CustomGestureListener _listener;
readonly GestureDetector _detector;
public GestureScrollViewRenderer(Context context) : base(context)
{
_listener = new CustomGestureListener();
_detector = new GestureDetector(context, _listener);
}
public override bool DispatchTouchEvent(MotionEvent e)
{
if (_detector != null)
{
_detector.OnTouchEvent(e);
base.DispatchTouchEvent(e);
return true;
}
return base.DispatchTouchEvent(e);
}
public override bool OnTouchEvent(MotionEvent ev)
{
base.OnTouchEvent(ev);
if (_detector != null)
return _detector.OnTouchEvent(ev);
return false;
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
{
_listener.OnSwipeLeft -= HandleOnSwipeLeft;
_listener.OnSwipeRight -= HandleOnSwipeRight;
}
if (e.OldElement == null)
{
_listener.OnSwipeLeft += HandleOnSwipeLeft;
_listener.OnSwipeRight += HandleOnSwipeRight;
}
}
void HandleOnSwipeLeft(object sender, EventArgs e) =>
((GestureScrollView)Element).OnSwipeLeft();
void HandleOnSwipeRight(object sender, EventArgs e) =>
((GestureScrollView)Element).OnSwipeRight();
}
public class CustomGestureListener : GestureDetector.SimpleOnGestureListener
{
static readonly int SWIPE_THRESHOLD = 100;
static readonly int SWIPE_VELOCITY_THRESHOLD = 100;
MotionEvent mLastOnDownEvent;
public event EventHandler OnSwipeLeft;
public event EventHandler OnSwipeRight;
public override bool OnDown(MotionEvent e)
{
mLastOnDownEvent = e;
return true;
}
public override bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
if (e1 == null)
e1 = mLastOnDownEvent;
float diffY = e2.GetY() - e1.GetY();
float diffX = e2.GetX() - e1.GetX();
if (Math.Abs(diffX) > Math.Abs(diffY))
{
if (Math.Abs(diffX) > SWIPE_THRESHOLD && Math.Abs(velocityX) > SWIPE_VELOCITY_THRESHOLD)
{
if (diffX > 0)
OnSwipeRight?.Invoke(this, null);
else
OnSwipeLeft?.Invoke(this, null);
}
}
return base.OnFling(e1, e2, velocityX, velocityY);
}
}
}
And in Xaml
Put the StackLayout in the ScrollView
<local:GestureScrollView SwipeRight="RightSwipe">
<StackLayout x:Name="firstLlayout" >
//...
</StackLayout>
</local:GestureScrollView>
I like and used Lucas Zhang's answer answer-59190549 to this question however, the GestureScrollView can be changed to utilize the same GestureRecognizer's that iOS will use like so:
public class GestureScrollView : ScrollView
{
private bool isInitialized = false;
private List<SwipeGestureRecognizer> LeftSwipeRecognizers { get; } = new();
private List<SwipeGestureRecognizer> RightSwipeRecognizers { get; } = new();
public GestureScrollView() : base()
{
}
protected override void LayoutChildren(double x, double y, double width, double height)
{
base.LayoutChildren(x, y, width, height);
//Not sure if this is the best place, but the ctor wasn't getting called.
if (!isInitialized)
{
isInitialized = true;
foreach (SwipeGestureRecognizer swipeGestureRecognizer in GestureRecognizers.Where(x => x is SwipeGestureRecognizer))
{
if (swipeGestureRecognizer.Direction.HasFlag(SwipeDirection.Left))
{
LeftSwipeRecognizers.Add(swipeGestureRecognizer);
}
if (swipeGestureRecognizer.Direction.HasFlag(SwipeDirection.Right))
{
RightSwipeRecognizers.Add(swipeGestureRecognizer);
}
}
}
}
private void ExecuteGestureCommands(List<SwipeGestureRecognizer> swipeRecognizers, SwipedEventArgs e)
{
foreach (var gesture in swipeRecognizers)
{
gesture.SendSwiped(this, e.Direction);
}
}
public void OnSwipeLeft(object sender, SwipedEventArgs e)
{
ExecuteGestureCommands(LeftSwipeRecognizers, e);
}
public void OnSwipeRight(object sender, SwipedEventArgs e)
{
ExecuteGestureCommands(RightSwipeRecognizers, e);
}
}
And the Renderer would need changes similar to these (Basically change all "EventArgs" references to "SwipedEventArgs" and pass in a new SwipedEventArgs on the event calls):
...
void HandleOnSwipeLeft(object sender, SwipedEventArgs e) => ((GestureScrollView)Element).OnSwipeLeft(sender, e);
void HandleOnSwipeRight(object sender, SwipedEventArgs e) => ((GestureScrollView)Element).OnSwipeRight(sender, e);
...
public event EventHandler<SwipedEventArgs> OnSwipeLeft;
public event EventHandler<SwipedEventArgs> OnSwipeRight;
...
if (diffX > 0)
{
OnSwipeRight?.Invoke(this, new SwipedEventArgs(null, SwipeDirection.Right));
}
else
{
OnSwipeLeft?.Invoke(this, new SwipedEventArgs(null, SwipeDirection.Left));
}
...
It's not perfect as it ignores the Up/Down directions in Android, but so does the other implementation.
Im trying to understand how all is connected and I need some help.
So far I can do insert, update, delete into sqlite database but I cant make the UI show the changes from the database automatically without me to update the ItemsSource on the ListView whenever changes are made to the database. i.e.:
In my App.xaml.cs
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
//Connection to the database and create the table if not there
string path = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "db.sqlite");
SQLiteConnection conn = new SQLiteConnection(path);
conn.CreateTable<CheckListItemModel>();
}
Than in my MainPage.xaml i have a simple page with two buttons and the Listview
<Page
x:Class="Personal_Checklist_2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Personal_Checklist_2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" >
<Button Content="Add Sample To Db" Click="Add_Sample_To_Db_Click" Margin="10,0,10,0" />
<Button Content="Clear Tasks" Click="Clear_Tasks_Click" />
</StackPanel>
<ListView Name="lvListView" Grid.Row="1" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
<TextBlock Text="{Binding Id}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
In MainPage.xaml.cs code-behind I also keep as simple as I know just to add new row into the table and clear the table
using Personal_Checklist_2.DataModels;
using System.IO;
using System.Linq;
using SQLite;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using System.Collections.ObjectModel;
namespace Personal_Checklist_2
{
public sealed partial class MainPage : Page
{
static string path = Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "db.sqlite");
ObservableCollection<CheckListItemModel> Tasks = new ObservableCollection<CheckListItemModel>();
public MainPage()
{
this.InitializeComponent();
using (var conn = new SQLiteConnection(path))
{
var query = conn.Table<CheckListItemModel>();
Tasks = new ObservableCollection<CheckListItemModel>(query.ToList());
lvTasksList.ItemsSource = Tasks.ToList(); //<<<--- If I dont do this, list is not updated
}
}
private void Add_Sample_To_Db_Click(object sender, RoutedEventArgs e)
{
var Task = new CheckListItemModel()
{
taskTitle = "Sample Task"
};
using (var conn = new SQLiteConnection(path))
{
conn.Insert(Task);
var query = conn.Table<CheckListItemModel>();
Tasks = new ObservableCollection<CheckListItemModel>(query.ToList());
lvTasksList.ItemsSource = Tasks.ToList(); //<<<--- If I dont do this, list is not updated
}
}
private void Clear_Tasks_Click(object sender, RoutedEventArgs e)
{
using (var conn = new SQLiteConnection(path))
{
conn.DeleteAll<CheckListItemModel>();
var query = conn.Table<CheckListItemModel>();
Tasks = new ObservableCollection<CheckListItemModel>(query.ToList());
lvTasksList.ItemsSource = Tasks.ToList(); //<<<--- If I dont do this, list is not updated
}
}
}
}
The database table model looks like this CheckListItemModel.cs
using SQLite;
using System;
using System.ComponentModel;
namespace Personal_Checklist_2.DataModels
{
class CheckListItemModel : INotifyPropertyChanged
{
#region Private fields on DataModel
private string _taskTitle;
#endregion
#region Public properties on DataModel
[PrimaryKey, AutoIncrement]
public int taskId { get; set; }
public string taskTitle
{
get { return _taskTitle; }
set { _taskTitle = value; NotifyPropertyChanged("taskTitle"); }
}
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
But it doesn't look right to me this way. Is there a way to achieve this without setting the listview.ItemsSource everytime some changes in db.sqlite and how?
You cast your ObservableCollection toList() thats why it doesnt update. Change it to:
lvTasksList.ItemsSource = Tasks;
And when you add an item you dont have to read again from db
private void Add_Sample_To_Db_Click(object sender, RoutedEventArgs e)
{
var Task = new CheckListItemModel()
{
taskTitle = "Sample Task"
};
using (var conn = new SQLiteConnection(path))
{
conn.Insert(Task);
}
Tasks.Add(Task);
}
You're creating a new ObservableCollection every time, instead of binding to a single ObservableCollection and changing the items in it.
So, keep Tasks as a single ObservableCollection and update its content based on the DB query results.