Reputation: 5
I have a custom slider and have tried to put it inside a listview with grids so the listview displays the following: Top left: items from a list as strings. Bottom left: (under each item) the custom slider. (To the right/the second column: an int/number value for each item).
The custom slider is made with the NuGet package SkiaSharp and is called "x:Name="balloon_slider"". My problem: When I put the custom slider inside my listview in xaml - it can no longer be refered to in the C# behind (does not exist in the current context). (It works outside the listview.) Here is the xaml code for the listview:
<ListView x:Name="displaylist100" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!--Items from list as strings-->
<Grid Grid.Column="0">
<Label x:Name="listlabel"
Text="{Binding Text}"
HorizontalTextAlignment="Center"
FontSize="Medium"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
Padding="10, 10"
Margin="0,5,5,0"/>
<!--SkiaSharp Balloon_slider-->
<Grid>
<skia:SKCanvasView x:Name="balloon_slider"
PaintSurface="Handle_Slider_PaintSurface"/>
<local:BalloonView x:Name="balloonSvg"/>
<Grid.Effects>
<local:TouchEffect
TouchAction="Handle_TouchAction"/>
</Grid.Effects>
</Grid>
<!--Value displayed to the right-->
<Label Text="{Binding DisplayValue, Mode=TwoWay}"
FontSize="Medium"
VerticalOptions="CenterAndExpand"
HorizontalOptions="Center"
Padding="10, 10"
Grid.Column="1"/>
</Grid>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The slider that I'm using is a customized version of this: "https://github.com/memsranga/Balloony".
I have tried to use this: "var balloon_slider = (SKCanvasView)listView.FindByName("balloon_slider");" in the "InitializeComponent ();" section - but it doesn't seem to make it connect the two. I have also tried to have a standard slider in the listview - which works. So my question is: how can I refer to the slider from my C# behind when it is in the listview?
Update: Here is the code behind from the balloon slider project - I'm a bit confused about what I need to change in order to do the binding. Currently it is each "balloon_slider" and "balloonSvg" which has the error that it can't be found:
using System;
using System.Diagnostics;
using System.Xml.Linq;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using Xamarin.Forms;
using static Balloony.TouchEffect;
namespace Balloony
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
SliderSelectedPaint = new SKPaint
{
Color = Color.FromHex("#eb0000").ToSKColor(),
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeWidth = SliderHeight
};
SliderUnSelectedPaint = new SKPaint
{
Color = Color.FromHex("#f8f8f8").ToSKColor(),
IsAntialias = true,
Style = SKPaintStyle.Stroke,
StrokeWidth = SliderHeight
};
ThumbPaint = new SKPaint
{
Color = Color.FromHex("#eb0000").ToSKColor(),
IsAntialias = true,
Style = SKPaintStyle.Fill
};
ThumbSelectedPaint = new SKPaint
{
Color = Color.FromHex("#eb0000").ToSKColor(),
IsAntialias = true,
Style = SKPaintStyle.Stroke,
StrokeWidth = SelectedThumbThickness
};
ThumbSelectedSubtractPaint = new SKPaint
{
Color = Color.Transparent.ToSKColor(),
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeWidth = 0,
BlendMode = SKBlendMode.Src
};
ThumbSubtractPaint = new SKPaint
{
Color = Color.Transparent.ToSKColor(),
IsAntialias = true,
Style = SKPaintStyle.Fill,
StrokeWidth = 0,
BlendMode = SKBlendMode.Src
};
Percent = 50;
}
float SliderHeight = 5;
float SelectedThumbThickness = 10;
float ThumbSize = 50;
SKPaint SliderSelectedPaint { get; set; }
SKPaint SliderUnSelectedPaint { get; set; }
SKPaint ThumbPaint { get; set; }
SKPaint ThumbSubtractPaint { get; set; }
SKPaint ThumbSelectedPaint { get; set; }
SKPaint ThumbSelectedSubtractPaint { get; set; }
private TouchActionType _touchType;
private double _width;
private float _percent;
public float Percent
{
get => _percent;
private set
{
_percent = value;
balloon_slider.InvalidateSurface();
TranslateBalloon(Percent);
}
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
// only calling when orientation changes
if (Math.Abs(width - _width) > 0.01)
{
_width = width;
balloonSvg.AnchorY = 1;
balloonSvg.TranslationX = (balloon_slider.Width * Percent / 100) - balloonSvg.Width / 2;
balloonSvg.Scale = 0;
balloonSvg.TranslationY = balloon_slider.Height;
}
}
void Handle_Slider_PaintSurface(object sender, SkiaSharp.Views.Forms.SKPaintSurfaceEventArgs e)
{
var info = e.Info;
var canvas = e.Surface.Canvas;
canvas.Clear();
DrawSlider(canvas, info, Percent);
DrawThumb(canvas, info, Percent, _touchType);
}
private void TranslateBalloon(float percent)
{
if (this.AnimationIsRunning("TranslationAnimation"))
{
return;
}
var oldX = balloonSvg.TranslationX;
var newX = balloon_slider.Width * percent / 100 - balloonSvg.Width / 2;
balloonSvg.Text = Math.Floor(Percent).ToString();
var translation = new Animation();
translation.Add(0, 1, new Animation((s) =>
{
if (oldX > newX)
{
var delta = oldX - s * Math.Abs(oldX - newX);
balloonSvg.TranslationX = delta;
}
else
{
var delta = oldX + s * Math.Abs(oldX - newX);
balloonSvg.TranslationX = delta;
}
}, 0, 1));
translation.Add(0, 1, new Animation(s =>
{
if (oldX > newX)
{
var delta = oldX - s * Math.Abs(oldX - newX);
var angle = Math.Abs(oldX - newX) > 0.001 ? Math.Tanh((oldX - newX) / balloon_slider.Width) : 0;
balloonSvg.Rotation = angle * 180;
}
else
{
var delta = oldX + s * Math.Abs(oldX - newX);
var angle = Math.Abs(oldX - newX) > 0.001 ? Math.Tanh((oldX - newX) / balloon_slider.Width) : 0;
balloonSvg.Rotation = angle * 180;
}
}, 0, 1, finished: () =>
{
balloonSvg.RelRotateTo(-balloonSvg.Rotation, 500);
}));
translation.Commit(balloonSvg, "TranslationAnimation", length: 100);
}
private void DrawThumb(SKCanvas canvas, SKImageInfo info, float percent, TouchActionType touchActionType)
{
var y = info.Height - ThumbSize - SelectedThumbThickness;
var center = info.Width * percent / 100;
if (touchActionType == TouchActionType.Pressed || touchActionType == TouchActionType.Moved)
{
// selected thumb
var radius = ThumbSize * 0.5f; // 50% of size
canvas.DrawCircle(center, y, radius, ThumbSelectedPaint);
canvas.DrawCircle(center, y, radius, ThumbSelectedSubtractPaint);
return;
}
//default thumb
var startX = center - ThumbSize / 2;
var startY = y - ThumbSize / 2;
var cornerRadius = ThumbSize * 0.4f; // 40% of size
var innerRadius = ThumbSize / 2 * .5f; // 50 % of side
canvas.DrawRoundRect(startX, startY, ThumbSize, ThumbSize, cornerRadius, cornerRadius, ThumbPaint);
canvas.DrawCircle(center, y, innerRadius, ThumbSubtractPaint);
}
private void Handle_TouchAction(object sender, Balloony.TouchEffect.TouchActionEventArgs args)
{
_touchType = args.Type;
if (this.AnimationIsRunning("FloatAnimation") || this.AnimationIsRunning("DropAnimation"))
{
return;
}
if (_touchType == TouchActionType.Pressed || _touchType == TouchActionType.Entered)
{
Debug.WriteLine("entered");
var floatAnimation = new Animation();
floatAnimation.Add(0, 1, new Animation((s) =>
{
balloonSvg.Scale = s;
balloonSvg.TranslationY = (balloon_slider.Height - balloonSvg.Height - 88) - s * -45;
}, 0, 1));
floatAnimation.Commit(balloonSvg, "FloatAnimation");
}
else if (_touchType == TouchActionType.Released || _touchType == TouchActionType.Exited)
{
var dropAnimation = new Animation();
dropAnimation.Add(0, 1, new Animation((s) =>
{
balloonSvg.Scale = s;
balloonSvg.TranslationY = (balloon_slider.Height - balloonSvg.Height - 88) - s * -45;
}, 1, 0));
dropAnimation.Commit(balloonSvg, "DropAnimation");
}
Percent = (float)((args.Location.X / balloon_slider.Width) * 100 - 15);
if (Percent > 100)
{
Percent = 100;
}
if (Percent < 0)
{
Percent = 0;
}
if (Percent < 20)
{
}
}
private void DrawSlider(SKCanvas canvas, SKImageInfo info, float percent)
{
var y = info.Height - ThumbSize - SelectedThumbThickness; // minus the thumb radius, minus thumb thickness
percent = Math.Min(percent, 100);
var selectX = info.Width * percent / 100;
canvas.DrawLine(0, y, selectX, y, SliderSelectedPaint);
canvas.DrawLine(selectX, y, info.Width, y, SliderUnSelectedPaint);
}
}
}
Upvotes: 0
Views: 108
Reputation: 13939
Just as Jason said, Elements inside templated controls cannot be referenced by Name.
If you add event to the SKCanvasView
inside of the ListView and implement this function in YourPage.xaml.cs
, you can get the View's instance by the paramter sender
.
Please refer to the following code:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void balloon_slider_PaintSurface(object sender, SkiaSharp.Views.Forms.SKPaintSurfaceEventArgs e)
{
SKCanvasView sKCanvasView = (SKCanvasView)sender;
}
}
Update:
In general, we recommend using MVVM
and data binding
to achieve this. For comparison, I added two buttons, one that responds to the click event using the Command
method, and one that adds the Clicked
event.
You can refer to the following code:
MyViewModel.cs
public class MyViewModel
{
public ObservableCollection<Item> Items { get; set; }
public ICommand RemoveItemCommand { get; set; }
public MyViewModel() {
Items = new ObservableCollection<Item>();
Items.Add( new Item { NumType = "S" , LocationCode = "0001"});
Items.Add(new Item { NumType = "M", LocationCode = "0002" });
Items.Add(new Item { NumType = "L", LocationCode = "0003" });
Items.Add(new Item { NumType = "S", LocationCode = "0001" });
Items.Add(new Item { NumType = "M", LocationCode = "0002" });
Items.Add(new Item { NumType = "L", LocationCode = "0003" });
RemoveItemCommand = new Command<Item>(RemoveItem);
}
private void RemoveItem(Item obj)
{
if (Items != null && Items.Contains(obj))
{
Items.Remove(obj);
}
}
}
Item.cs
public class Item: INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set
{
SetProperty(ref _isChecked, value);
}
}
public string NumType { get; set; }
public string LocationCode { get; set; }
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xamlistviewapp131="clr-namespace:XamListViewApp131"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="XamListViewApp131.MainPage"
>
<ContentPage.BindingContext>
<xamlistviewapp131:MyViewModel></xamlistviewapp131:MyViewModel>
</ContentPage.BindingContext>
<StackLayout Orientation="Vertical"
Spacing="25"
Padding="30,0"
VerticalOptions="Center"
>
<ListView Grid.Row="1" x:Name="listview" ItemsSource="{Binding Items}" >
<ListView.Header>TheHeader</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="{Binding NumType}" Margin="0,0,0,0" />
<Label Grid.Row="0" Grid.Column="1" Text="{Binding LocationCode}" Margin="0,0,0,0" />
<Button Text="Remove" Grid.Column="2" Command="{Binding BindingContext.RemoveItemCommand, Source={x:Reference listview}}" CommandParameter="{Binding .}" />
<Button Text="Test" Grid.Column="3" Clicked="Button_Clicked" CommandParameter="{Binding .}" />
<skia:SKCanvasView x:Name="balloon_slider" Grid.Column="4" BackgroundColor="Yellow" WidthRequest="50" HeightRequest="50"
PaintSurface="balloon_slider_PaintSurface" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
MainPage.xaml.cs
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void balloon_slider_PaintSurface(object sender, SkiaSharp.Views.Forms.SKPaintSurfaceEventArgs e)
{
SKCanvasView sKCanvasView = (SKCanvasView)sender;
}
private void Button_Clicked(object sender, EventArgs e)
{
Button button = (Button)sender;
Item item= button.CommandParameter as Item;
System.Diagnostics.Debug.WriteLine("click item is: "+item.NumType);
}
}
Upvotes: 0