how to access child elements in a collection view? - xamarin.forms

i am making frames in collection view and i want to change the background color of frames whenever i select the frames but the problem is that i cannot access my frames from the collection view and when i click on the frame it picks the default orange color.
i made small changes in my frame model class where i want to change text color of my label as like we did the color of frame here is the code but it is not working
this code is in frame model class
public Color LabelColor
{
set
{
if (txtcolor != value)
{
txtcolor = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("labelcolor"));
}
}
}
get
{
return firstFrameBackColor;
}
}
here i access this in my CS Class
FrameModel previous = (e.PreviousSelection.FirstOrDefault() as FrameModel);
FrameModel current = (e.CurrentSelection.FirstOrDefault() as FrameModel);
//Set the current to the color you want
try
{
current.FirstFrameBackColor = Color.FromRgb(74, 152, 247);
current.LabelColor = Color.White;
// current.SecondFrameBackColor = Color.Green;
}
catch (Exception ex)
{
ex.Message.ToString();
throw;
}
// current.SecondFrameBackColor = Color.Green;
if (previous != null)
{
//Reset the previous to defaulr color
previous.FirstFrameBackColor = Color.White;
current.LabelColor = Color.Black;
//previous.SecondFrameBackColor = Color.Purple;
}

when i give x:name property to my child views inside the collection
view i cant access them in my C# class
You can't access items in templates from the code behind by name because there may be 0 or 1000 copies of that template created at run time.
Refer this discussion: cannot-reach-control-x-name-inside-listview
In your case, instead of access the control in the template, you can set the background property binding to the property in the model, for example:
<Frame
WidthRequest="20"
HeightRequest="20"
Margin="0,-30,0,10"
HorizontalOptions="End"
CornerRadius="10"
Padding="5"
BackgroundColor="{Binding SecondFrameBackColor}">
<Label
Text="5"
TextColor="#FFFFFF"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
>
</Label>
</Frame>
Here is the model:
class myModel : INotifyPropertyChanged
{
Color firstFrameBackColor;
Color secondFrameBackColor;
public event PropertyChangedEventHandler PropertyChanged;
public myModel()
{
}
public Color FirstFrameBackColor
{
set
{
if (firstFrameBackColor != value)
{
firstFrameBackColor = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("FirstFrameBackColor"));
}
}
}
get
{
return firstFrameBackColor;
}
}
public Color SecondFrameBackColor
{
set
{
if (secondFrameBackColor != value)
{
secondFrameBackColor = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SecondFrameBackColor"));
}
}
}
get
{
return secondFrameBackColor;
}
}
}
The itemSoure:
public partial class MainPage : ContentPage
{
ObservableCollection<myModel> models = new ObservableCollection<myModel>();
public MainPage()
{
InitializeComponent();
myModel model1 = new myModel() { FirstFrameBackColor = Color.White, SecondFrameBackColor = Color.Purple };
myModel model2 = new myModel() { FirstFrameBackColor = Color.White, SecondFrameBackColor = Color.Purple };
myModel model3 = new myModel() { FirstFrameBackColor = Color.White, SecondFrameBackColor = Color.Purple };
myModel model4 = new myModel() { FirstFrameBackColor = Color.White, SecondFrameBackColor = Color.Purple };
myModel model5 = new myModel() { FirstFrameBackColor = Color.White, SecondFrameBackColor = Color.Purple };
myModel model6 = new myModel() { FirstFrameBackColor = Color.White, SecondFrameBackColor = Color.Purple };
myModel model7 = new myModel() { FirstFrameBackColor = Color.White, SecondFrameBackColor = Color.Purple };
models.Add(model1);
models.Add(model2);
models.Add(model3);
models.Add(model4);
models.Add(model5);
models.Add(model6);
models.Add(model7);
CNlist.ItemsSource = models;
}
And in the SelectionChanged event, change the background to what you want:
private void CNlist_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
myModel previous = (e.PreviousSelection.FirstOrDefault() as myModel) ;
myModel current = (e.CurrentSelection.FirstOrDefault() as myModel);
//Set the current to the color you want
current.FirstFrameBackColor = Color.Pink;
current.SecondFrameBackColor = Color.Green;
if (previous != null)
{
//Reset the previous to defaulr color
previous.FirstFrameBackColor = Color.White;
previous.SecondFrameBackColor = Color.Purple;
}
}
Here is the result:
I updated my sample here and you can check it. Let me know if it works to you!

From the CollectionView Spec:
The orange color is actually the value for state_activated in your
Android app's theme. So not everyone will see orange; that's just the
AppCompat default. This is the fallback value if nothing else has been
specified.
When an item in the CollectionView is selected, the VisualState for
the root Forms element of the item is changed to Selected. You can use
the VisualStateManager to manage what a selected item looks like. For
an example of this, take a look at the SelectionModeGallery in Control
Gallery. On that page, the background color for the selected item is
being set to LightSkyBlue. Any other Forms property can also be set;
for instance, try adding to
the tag.
At the moment, this is somewhat limited; you can really only modify
the root element in your ItemTemplate.
So, to answer your question, it will not work, since your Frame is not the Root element from your ItemTemplate. Either you apply that in your StackLAyout, or in the SelectedItem, you change manually the background color of your element.

Related

Skaisharp Bind CroppedBitmap

I followed this tutorial to create an Image cropping page https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/bitmaps/cropping. However i now bound two views of the PhotoCropperCanvasView to an carousel view an wanted to Bind the CroppedBitMap so that i can access this property directl in the viewmodel. I just cannot figure ouit how i would achieve that. I f i just make this a Bindable Property the property does not change when i make a new rectangle. So i think i kind of have to exclude the Code of the property but i am very confused.
The whole ocde:
public class PhotoCropperCanvasView : SKCanvasView, INotifyPropertyChanged
{
const int CORNER = 50; // pixel length of cropper corner
const int RADIUS = 100; // pixel radius of touch hit-test
//SKBitmap bitmap;
//CroppingRectangle croppingRect;
SKMatrix inverseBitmapMatrix;
public SKBitmap testmap;
//public SKBitmap bitmap { get; set; }
public Image testImage { get; set; }
public static readonly BindableProperty mapProperty =
BindableProperty.Create(nameof(map), typeof(SKBitmap), typeof(PhotoCropperCanvasView), null);
public SKBitmap map
{
get
{
return (SKBitmap)GetValue(mapProperty);
}
set
{
SetValue(mapProperty, value);
}
}
public CroppingRectangle croppingRect { get; set; }
//public SKMatrix inverseBitmapMatrix { get; set; }
public static readonly BindableProperty bitmapProperty =
BindableProperty.Create(nameof(bitmap), typeof(SKBitmap), typeof(Image),null,propertyChanged: OnbitmapChanged);
static void OnbitmapChanged(BindableObject bindable, object oldValue, object newValue)
{
Console.WriteLine("test");
}
public SKBitmap bitmap
{
get
{
return (SKBitmap)GetValue(bitmapProperty);
}
set
{
SetValue(bitmapProperty, value);
}
}
public SKBitmap CroppedBitmap
{
get
{
SKRect cropRect = new SKRect(croppingRect.Rect.Left,croppingRect.Rect.Top,croppingRect.Rect.Right,croppingRect.Rect.Bottom);
SKBitmap croppedBitmap = new SKBitmap((int)cropRect.Width,
(int)cropRect.Height);
SKRect dest = new SKRect(0, 0, cropRect.Width, cropRect.Height);
SKRect source = new SKRect(cropRect.Left, cropRect.Top,
cropRect.Right, cropRect.Bottom);
using (SKCanvas canvas = new SKCanvas(croppedBitmap))
{
canvas.DrawBitmap(bitmap, source, dest);
}
return croppedBitmap;
}
}
// Touch tracking
TouchEffect touchEffect = new TouchEffect();
struct TouchPoint
{
public int CornerIndex { set; get; }
public SKPoint Offset { set; get; }
}
Dictionary<long, TouchPoint> touchPoints = new Dictionary<long, TouchPoint>();
// Drawing objects
SKPaint cornerStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.White,
StrokeWidth = 10
};
SKPaint edgeStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.White,
StrokeWidth = 2
};
// this constructor for profile image
public PhotoCropperCanvasView(SKBitmap bitmap, float? aspectRatio = null)
{
this.bitmap = bitmap;
SKRect bitmapRect = new SKRect(0, 0, bitmap.Width, bitmap.Height);
croppingRect = new CroppingRectangle(bitmapRect, aspectRatio);
touchEffect.TouchAction += OnTouchEffectTouchAction;
}
// this constructor for post images
public PhotoCropperCanvasView()
{
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = nameof(bitmap))
{
base.OnPropertyChanged(propertyName);
if (bitmap != null)
{
SKRect bitmapRect = new SKRect(0, 0, bitmap.Width, bitmap.Width);
croppingRect = new CroppingRectangle(bitmapRect, 1);
touchEffect.TouchAction += OnTouchEffectTouchAction;
}
}
protected override void OnParentSet()
{
base.OnParentSet();
// Attach TouchEffect to parent view
Parent.Effects.Add(touchEffect);
}
protected override void OnPaintSurface(SKPaintSurfaceEventArgs args)
{
base.OnPaintSurface(args);
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear(SKColors.Gray);
// Calculate rectangle for displaying bitmap
float scale = Math.Min((float)info.Width / bitmap.Width, (float)info.Height / bitmap.Height);
float x = (info.Width - scale * bitmap.Width) / 2;
float y = (info.Height - scale * bitmap.Height) / 2;
SKRect bitmapRect = new SKRect(x, y, x + scale * bitmap.Width, y + scale * bitmap.Height);
canvas.DrawBitmap(bitmap, bitmapRect);
// Calculate a matrix transform for displaying the cropping rectangle
SKMatrix bitmapScaleMatrix = SKMatrix.MakeIdentity();
bitmapScaleMatrix.SetScaleTranslate(scale, scale, x, y);
// Display rectangle
SKRect scaledCropRect = bitmapScaleMatrix.MapRect(croppingRect.Rect);
canvas.DrawRect(scaledCropRect, edgeStroke);
// Display heavier corners
using (SKPath path = new SKPath())
{
path.MoveTo(scaledCropRect.Left, scaledCropRect.Top + CORNER);
path.LineTo(scaledCropRect.Left, scaledCropRect.Top);
path.LineTo(scaledCropRect.Left + CORNER, scaledCropRect.Top);
path.MoveTo(scaledCropRect.Right - CORNER, scaledCropRect.Top);
path.LineTo(scaledCropRect.Right, scaledCropRect.Top);
path.LineTo(scaledCropRect.Right, scaledCropRect.Top + CORNER);
path.MoveTo(scaledCropRect.Right, scaledCropRect.Bottom - CORNER);
path.LineTo(scaledCropRect.Right, scaledCropRect.Bottom);
path.LineTo(scaledCropRect.Right - CORNER, scaledCropRect.Bottom);
path.MoveTo(scaledCropRect.Left + CORNER, scaledCropRect.Bottom);
path.LineTo(scaledCropRect.Left, scaledCropRect.Bottom);
path.LineTo(scaledCropRect.Left, scaledCropRect.Bottom - CORNER);
canvas.DrawPath(path, cornerStroke);
}
// Invert the transform for touch tracking
bitmapScaleMatrix.TryInvert(out inverseBitmapMatrix);
}
void OnTouchEffectTouchAction(object sender, TouchActionEventArgs args)
{
int i = 0;
SKPoint pixelLocation = ConvertToPixel(args.Location);
SKPoint bitmapLocation = inverseBitmapMatrix.MapPoint(pixelLocation);
switch (args.Type)
{
case TouchActionType.Pressed:
// Convert radius to bitmap/cropping scale
float radius = inverseBitmapMatrix.ScaleX * RADIUS;
// Find corner that the finger is touching
int cornerIndex = croppingRect.HitTest(bitmapLocation, radius);
if (cornerIndex != -1 && !touchPoints.ContainsKey(args.Id))
{
TouchPoint touchPoint = new TouchPoint
{
CornerIndex = cornerIndex,
Offset = bitmapLocation - croppingRect.Corners[cornerIndex]
};
touchPoints.Add(args.Id, touchPoint);
}
break;
case TouchActionType.Moved:
if (touchPoints.ContainsKey(args.Id))
{
TouchPoint touchPoint = touchPoints[args.Id];
croppingRect.MoveCorner(touchPoint.CornerIndex,
bitmapLocation - touchPoint.Offset);
InvalidateSurface();
}
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
if (touchPoints.ContainsKey(args.Id))
{
touchPoints.Remove(args.Id);
//map = CroppedBitmap;
}
break;
}
}
SKPoint ConvertToPixel(Xamarin.Forms.Point pt)
{
return new SKPoint((float)(CanvasSize.Width * pt.X / Width),
(float)(CanvasSize.Height * pt.Y / Height));
}
}
}
Since the case TouchActionType.Cancelled gets only triggerd once everytime the rectangel was moved, i thought i would set thew bindable Proeprty map to the Cropped bitmap property so that i can get the Cropped Image from the view obver a Binding to the viewmodel. This part works, however, when i activate the line map = CroppedBitmap the cropping rectangle can only be moved by opposite corners. So if i start moving it with the bottom right corner i con only use the top left or bottom right. If i leave the line map = CroppedBitman(249) deactivated i can move the rectangle on all corners at every times. I do not understand this behaviour.
the view:
<CarouselView Grid.Row="0"
IsSwipeEnabled="False"
x:Name="carousel"
Margin="0,-40,0,0"
CurrentItem="{Binding CurrentCutImage, Mode=TwoWay}"
CurrentItemChanged="CarouselView_CurrentItemChanged"
HorizontalScrollBarVisibility="Always"
IsScrollAnimated="True"
ItemsSource="{Binding ImageObjects}"
VerticalScrollBarVisibility="Always"
>
<CarouselView.ItemTemplate>
<DataTemplate x:DataType="viewmodel:CutImages">
<Grid>
<bitmaps:PhotoCropperCanvasView bitmap="{Binding ImageSource }" map="{Binding MapSource, Mode=TwoWay}" >
</bitmaps:PhotoCropperCanvasView>
</Grid>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
and the VM:
public partial class CutImagesViewModel : ObservableObject
{
// == observable properties ==
[ObservableProperty]
public Collection<CutImages> imageObjects = new Collection<CutImages>();
[ObservableProperty]
CutImages currentCutImage;
[ObservableProperty]
public SKBitmap maps;
public CutImagesViewModel(Collection<SKBitmapImageSource> images)
{
foreach(var image in images)
{
ImageObjects.Add(new CutImages(image));
}
this.CurrentCutImage = this.ImageObjects.FirstOrDefault();
}
}
public partial class CutImages : ObservableObject
{
[ObservableProperty]
public SKBitmap imageSource;
[ObservableProperty]
public SKBitmap mapSource;
partial void OnMapSourceChanged(SKBitmap value)
{
if(!images.Contains(value))
{
images.Add(value);
}
}
[ObservableProperty]
Collection<SKBitmap> images = new Collection<SKBitmap>();
public CutImages(ImageSource imageSource)
{
SKBitmapImageSource sourceImage = (SKBitmapImageSource)imageSource;
SKBitmap image = sourceImage;
ImageSource = image;
}
}

How to assign the grid to a grid

I new a grid object without any settings in the xaml file.
After importing the data through 'Build_Grid()' in the 'OnAppearing()', I want to assign the 'gridview' to 'Grid_Info' to display on the screen, the code 'Grid_Info = gridview' does not work.
I am wondering how to achieve my needs?
<ContentPage.Content>
<StackLayout>
<Label Text="AAA" />
<Grid x:Name="Grid_Info">
</Grid>
</StackLayout>
</ContentPage.Content>
void Build_Grid(Data data)
{
Grid gridview = new Grid();
gridview.RowDefinitions.Add(new RowDefinition() { Height = 40 });
gridview.Children.Add(data[0],0,0);
...
Grid_Info = gridview; //it does not work...
}
protected override void OnAppearing()
{
Data data = new Data();
...
Build_Grid(data);
}
if you've already defined the Grid in XAML there is no need to do this
Grid gridview = new Grid();
instead just reference Grid_Info directly
Grid_Info.RowDefinitions.Add(new RowDefinition() { Height = 40 });
Grid_Info.Children.Add(data[0],0,0);
You could try the code below. I make a Data class with view to test. It work for me.
public partial class Page3 : ContentPage
{
Data[] data;
public Page3()
{
InitializeComponent();
data = new Data[2];
data[0] = new Data { view = new Button() { BackgroundColor = Color.Red } }; //data[0]
data[1] = new Data { view = new Label() { BackgroundColor = Color.Green, Text = "Label" } };//data[1]
}
void Build_Grid(Data[] data)
{
Grid gridview = new Grid();
Grid_Info.RowDefinitions.Add(new RowDefinition() { Height = 40 });
Grid_Info.Children.Add(data[0].view, 0, 0);//show the red button
Grid_Info = gridview; //it does not work...
}
protected override void OnAppearing()
{
base.OnAppearing();
Build_Grid(data);
}
}
public class Data
{
public View view { get; set; }
}

Height Button in iOS (Xamarin Forms) dont resize when text label wrap word (I want dynamic size)

I have a ListView in Xamarin.Forms of this way :
this.listView = new ListView();
this.listView.HasUnevenRows = true;
var dataTemplate = new DataTemplate(() =>
{
return new ViewCell { View = new CustomButtonTemplate()};
});
this.listView.ItemTemplate = dataTemplate;
CustomButtonTemplate.xaml
<local:CustomButton
Margin="6"
Padding="0"
HeightRequest="-1"
WidthRequest="-1"
Style="{StaticResource Title_LabelStyle}"
Text="{Binding DisplayText}" />
I also got one button renderer but dont work (without HeightRequest,WidthRequest,Padding dont work either):
[assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButtonMultilineRenderer))]
namespace SGUK.ClassAction.IOS.Renderers
{
public class CustomButtonMultilineRenderer : ButtonRenderer
{
public CustomButtonMultilineRenderer()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
{
base.OnElementChanged(e);
if (this.Control != null)
{
this.Control.TitleLabel.LineBreakMode = UILineBreakMode.WordWrap;
this.Control.TitleEdgeInsets = new UIEdgeInsets(0, 10, 0, 10);
this.Control.TitleLabel.TextAlignment = UITextAlignment.Center;
this.Control.HorizontalAlignment = UIControlContentHorizontalAlignment.Center;
}
}
}
}
(with MaterialButtonRenderer dont work either)
The auto height with HasUnevenRows=true works fine on iOS if not using a custom renderer. If using a custom renderer, then it is up to the renderer to set the height of the cell, you have to calculate your own row height in the GetHeightForRow method in the custom renderer.
[assembly: ExportRenderer(typeof(ListView), typeof(MyLVRenderer))]
namespace App79.iOS
{
public class MyLVRenderer : ListViewRenderer
{
//UITableViewSource originalSource;
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
UITableViewSource originalSource = (UIKit.UITableViewSource)Control.Source;
Control.Source = new MyLVSource(originalSource, e.NewElement);
}
}
public class MyLVSource : UITableViewSource
{
UITableViewSource originalSource;
ListView myListView;
public MyLVSource(UITableViewSource origSource, ListView myListV)
{
originalSource = origSource;
myListView = myListV;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return originalSource.RowsInSection(tableview, section);
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
return originalSource.GetCell(tableView, indexPath);
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
return originalSource.GetHeightForFooter(tableView, section);
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
nfloat origHeight = originalSource.GetHeightForRow(tableView, indexPath);
// calculate your own row height here
ObservableCollection<Employee> employees = myListView.ItemsSource as ObservableCollection<Employee>;
string displayName = employees[indexPath.Row].DisplayName;
nfloat height = MeasureTextSize(displayName,UIScreen.MainScreen.Bounds.Size.Width-50,UIFont.SystemFontSize,null);
return height;
}
public nfloat MeasureTextSize(string text, double width, double fontSize, string fontName = null)
{
var nsText = new NSString(text);
var boundSize = new SizeF((float)width, float.MaxValue);
var options = NSStringDrawingOptions.UsesFontLeading | NSStringDrawingOptions.UsesLineFragmentOrigin;
if (fontName == null)
{
fontName = "HelveticaNeue";
}
var attributes = new UIStringAttributes
{
Font = UIFont.FromName(fontName, (float)fontSize)
};
var sizeF = nsText.GetBoundingRect(boundSize, options, attributes, null).Size;
//return new Xamarin.Forms.Size((double)sizeF.Width, (double)sizeF.Height);
return sizeF.Height + 5;
}
}
}
Here is the result:
I uploaded a sample here and you can check.

How do I hide Xamarin.Forms TableView header/footer with a custom renderer on iOS?

I am wanting to create a page layout where I display multiple TableViews along with other views such as a Label in between each.
In doing this I need the ability to:
Make the TableView not default to a vertically expanded layout option (the default which does not seem changeable) so that they can be stacked in a StackLayout vertically.
Hide the corresponding UITableView section header/footers so that there isn't excess vertical spacing between.
I have achieved this with the following CustomTableView control:
public class CustomTableView : TableView
{
public bool NoHeader { get; set; }
public bool NoFooter { get; set; }
protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
{
if(!VerticalOptions.Expands)
{
// Call OnSizeRequest that is overwritten in custom renderer
var baseOnSizeRequest = GetVisualElementOnSizeRequest();
return baseOnSizeRequest(widthConstraint, heightConstraint);
}
return base.OnSizeRequest(widthConstraint, heightConstraint);
}
public Func<double, double, SizeRequest> GetVisualElementOnSizeRequest()
{
var handle = typeof(VisualElement).GetMethod(
"OnSizeRequest",
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new Type[] { typeof(double), typeof(double) },
null)?.MethodHandle;
var pointer = handle.Value.GetFunctionPointer();
return (Func<double, double, SizeRequest>)Activator.CreateInstance(
typeof(Func<double, double, SizeRequest>), this, pointer);
}
}
and the custom iOS renderer for this which follows the advise from this SO answer for hiding UITableView headers/footers:
public class CustomTableViewRenderer : TableViewRenderer
{
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
Control.LayoutIfNeeded();
var size = new Size(Control.ContentSize.Width, Control.ContentSize.Height);
return new SizeRequest(size);
}
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if(e.NewElement is CustomTableView view)
{
Control.ScrollEnabled = false;
SetSource();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (CustomTableView)Element;
if(e.PropertyName == TableView.HasUnevenRowsProperty.PropertyName)
{
SetSource();
}
}
private void SetSource()
{
var view = (CustomTableView)Element;
if(view.NoFooter || view.NoHeader)
{
Control.Source = new CustomTableViewModelRenderer(view);
}
else
{
Control.Source = Element.HasUnevenRows ? new UnEvenTableViewModelRenderer(Element) :
new TableViewModelRenderer(Element);
}
}
public class CustomTableViewModelRenderer : UnEvenTableViewModelRenderer
{
private readonly CustomTableView _view;
public CustomTableViewModelRenderer(CustomTableView model)
: base(model)
{
_view = model;
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
if(_view.HasUnevenRows)
{
return UITableView.AutomaticDimension;
}
return base.GetHeightForRow(tableView, indexPath);
}
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
{
if(_view.NoHeader)
{
return 0.00001f;
}
return base.GetHeightForHeader(tableView, section);
}
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
if(_view.NoHeader)
{
return new UIView(new CGRect(0, 0, 0, 0));
}
return base.GetViewForHeader(tableView, section);
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
if(_view.NoFooter)
{
return 0.00001f;
}
return 10f;
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
if(_view.NoFooter)
{
return new UIView(new CGRect(0, 0, 0, 0));
}
return base.GetViewForFooter(tableView, section);
}
}
}
I then create a simple Xamarin.Forms ContentPage to implement this as such:
public class TablePage : ContentPage
{
public TablePage()
{
Table1 = new PageTableView
{
BackgroundColor = Color.Cyan,
Root = new TableRoot
{
new TableSection("Table 1")
{
new LabelEntryTableCell("Label A"),
new LabelEntryTableCell("Label B")
}
}
};
var label1 = new Label
{
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Vivamus accumsan lacus orci. Nulla in enim erat.",
Margin = new Thickness(15, 5, 15, 0),
BackgroundColor = Color.Yellow
};
Table2 = new PageTableView
{
BackgroundColor = Color.Red,
Root = new TableRoot
{
new TableSection("Table 2")
{
new LabelEntryTableCell("Label C"),
new LabelEntryTableCell("Label D")
}
}
};
var label2 = new Label
{
Text = "Duis vulputate mattis elit. " +
"Donec vulputate lorem vitae elit posuere, quis consequat ligula imperdiet.",
Margin = new Thickness(15, 5, 15, 0),
BackgroundColor = Color.Green
};
StackLayout = new StackLayout
{
Children = { Table1, label1, Table2, label2 },
Spacing = 0
};
BackgroundColor = Color.Gray;
Title = "Custom Table View";
Content = new ScrollView
{
Content = StackLayout
};
}
public PageTableView Table1 { get; set; }
public PageTableView Table2 { get; set; }
public StackLayout StackLayout { get; set; }
}
public class PageTableView : CustomTableView
{
public PageTableView()
{
Intent = TableIntent.Settings;
HasUnevenRows = true;
RowHeight = -1;
VerticalOptions = LayoutOptions.Start;
NoFooter = true;
}
}
public class LabelEntryTableCell : ViewCell
{
public LabelEntryTableCell(string labelText)
{
var label = new Label { Text = labelText };
var entry = new Entry();
View = new StackLayout
{
Children = { label, entry },
BackgroundColor = Color.White,
Padding = 10
};
}
}
This all seems to work as expected, however, when the TablePage first displays much of the CustomTableView content is chopped off. I can then rotate the device to landscape and the problem corrects itself, showing my layout as I intend. Then rotating then device back to portrait still shows the correct layout with no more information from the CustomTableView being chopped off anymore in that orientation either. This happens consistently every time. See a demo of this here:
I can also fix the problem by adding this to the TablePage:
protected override void OnAppearing()
{
base.OnAppearing();
// This will also fix the problem, but you see a flash of the old layout
// when the page first displays.
StackLayout.RedrawLayout();
}
...
public class CustomStackLayout : StackLayout
{
public void RedrawLayout()
{
InvalidateLayout();
}
}
How do I correct this problem? My custom control/renderer/page works as intended whenever I do the rotation/OnAppearing hack to correct it and I am not sure why. It seems like I need to call some re-draw method on the UITableView.
Sample Project
If you want to see the full source/project for this isolated test scenario you can find it on GitHub here.
I knew this issue in iOS. When the tableView first loaded, we can't get the correct contentSize even if we have written LayoutIfNeeded(). But after the page is appearing then we refresh it, we can get the correct contentSize. This is why
This will also fix the problem, but you see a flash of the old layout
Solution1: My suggestion is we can try to refresh the tableView in a bit milliseconds delay when the tableView will dispaly:
In the TablePage, I subscribe a MessagingCenter to refresh the layout
MessagingCenter.Subscribe<object>(this, "Refresh", (sender) =>
{
StackLayout.RedrawLayout();
});
Then I send the message in the renderer:
//I define this bool field to make sure this message only be sent at the first time after tableView loaded
private bool shouldRefresh = true;
public override async void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
{
if (tableView.NumberOfRowsInSection(0) - 1 == indexPath.Row)
{
if (shouldRefresh)
{
await Task.Delay(10);
MessagingCenter.Send<object>(this, "Refresh");
shouldRefresh = false;
}
}
}
Solutin2: We can also try to define an estimated rowHeight to give a contentSize when the tableView first loaded:
public override nfloat EstimatedHeight(UITableView tableView, NSIndexPath indexPath)
{
return 100;
}
But in this solution the height must be predefined and calculated by ourselves.

Multi-Line text Button Xamarin.Forms

theres is a way to set a multi-line text to a Xamarin.Forms Button??
I've tried Button.Text = "something \n xxjjjxx" But don't work.
A simple solution will use:
There is an excellent example on how to achieve this on Github. It is quite simple really. Just create your own control that inherits from ContentView and contains a grid.
[ContentProperty("Content")]
public class MultiLineButton : ContentView
{
public event EventHandler Clicked;
protected Grid ContentGrid;
protected ContentView ContentContainer;
protected Label TextContainer;
public String Text
{
get
{
return (String)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
OnPropertyChanged();
RaiseTextChanged();
}
}
public new View Content
{
get { return ContentContainer.Content; }
set
{
if (ContentGrid.Children.Contains(value))
return;
ContentContainer.Content = value;
}
}
public static BindableProperty TextProperty = BindableProperty.Create(
propertyName: "Text",
returnType: typeof(String),
declaringType: typeof(MultiLineButton),
defaultValue: null,
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: TextValueChanged);
private static void TextValueChanged(BindableObject bindable, object oldValue, object newValue)
{
((MultiLineButton)bindable).TextContainer.Text = (String)newValue;
}
public event EventHandler TextChanged;
private void RaiseTextChanged()
{
if (TextChanged != null)
TextChanged(this, EventArgs.Empty);
}
public MultiLineButton()
{
ContentGrid = new Grid
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand
};
ContentGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
ContentGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });
ContentContainer = new ContentView
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
TextContainer = new Label
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
ContentContainer.Content = TextContainer;
ContentGrid.Children.Add(ContentContainer);
var button = new Button
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
BackgroundColor = Color.FromHex("#01000000")
};
button.Clicked += (sender, e) => OnClicked();
ContentGrid.Children.Add(button);
base.Content = ContentGrid;
}
public void OnClicked()
{
if (Clicked != null)
Clicked(this, new EventArgs());
}
}
Then it can be used like this:
<local:MultiLineButton x:Name="AssessmentToolDetailButton"
WidthRequest="100" HeightRequest="60" BackgroundColor="Blue">
<StackLayout HorizontalOptions="Center" VerticalOptions="CenterAndExpand">
<Label Text="Hello" TextColor="White" Font="16"/>
<Label Text="World" TextColor="White" Font="16"/>
</StackLayout>
</local:MultiLineButton>
You can also place an image in the button by setting its content.
In my example I modified Dans original code in order to make the text bindable. Just set the Text value instead of the Content like this:
<local:MultiLineButton Text="{Binding Description}" />
All credit goes to Danvanderboom for his example:
ConentButton by Danvanderboom
This is mainly a problem with iOS because Android will wrap the text
by default. I tried the solution provided by Kasper and it worked
however the buttons do not have rounded corners and the appearance is
not consistent with other buttons in my app.
A simple solution is to use a custom renderer (ButtonRenderer) to set the LineBreakMode to WordWrap. If you then set the width of the button in the Xaml you get words to appear on different lines.
iOS
public class WrappedButtonRenderer: ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
Control.TitleEdgeInsets = new UIEdgeInsets(4, 4, 4, 4);
Control.TitleLabel.LineBreakMode = UILineBreakMode.WordWrap;
Control.TitleLabel.TextAlignment = UITextAlignment.Center;
}
}
Android does not require a custom renderer because it wraps by default.
This is a known issue with Xamarin Forms.
I don't think I've seen two lined buttons often. You have two options that I think might work:
Create a Custom Renderer and Extend the respective Button Class to do more on each native platform. Might be a harder
Create a Xamarin.Forms Class that extends a View that can contains a StackLayout and smaller elements such as multi-line labels, then you can use a TapGestureRecognizer to use with your view and treat it like a button.
Expanding on fireydude's answer, I created a MultilineButton control and renderer for iOS so I could add text alignment. This uses the Xamarin.Forms.TextAlignment enum.
MultilineButton.cs
using Xamarin.Forms;
namespace APP_NAMESPACE.Controls
{
public class MultilineButton : Button
{
public static readonly BindableProperty HorizontalTextAlignmentProperty = BindableProperty.Create(
propertyName: "HorizontalTextAlignment",
returnType: typeof(TextAlignment),
declaringType: typeof(MultilineButton),
defaultValue: TextAlignment.Start
);
public TextAlignment HorizontalTextAlignment
{
get { return (TextAlignment)GetValue(HorizontalTextAlignmentProperty); }
set { SetValue(HorizontalTextAlignmentProperty, value); }
}
}
}
MultilineButtonRenderer.cs
using APP_NAMESPACE.Controls;
using APP_NAMESPACE.iOS.Renderers;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(MultilineButton), typeof(MultilineButtonRenderer))]
namespace APP_NAMESPACE.iOS.Renderers
{
public class MultilineButtonRenderer : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control == null) { return; }
UIControlContentHorizontalAlignment horizontalAlignment;
UITextAlignment textAlignment;
// We have to use ButtonRenderer, so cast the Element to MultilineButton to get the HorizontalTextAlignment property
var button = (MultilineButton)Element;
if (button == null) { return; }
switch(button.HorizontalTextAlignment)
{
case TextAlignment.Center:
horizontalAlignment = UIControlContentHorizontalAlignment.Center;
textAlignment = UITextAlignment.Center;
break;
case TextAlignment.End:
horizontalAlignment = UIControlContentHorizontalAlignment.Right;
textAlignment = UITextAlignment.Right;
break;
default:
horizontalAlignment = UIControlContentHorizontalAlignment.Left;
textAlignment = UITextAlignment.Left;
break;
}
Control.HorizontalAlignment = horizontalAlignment;
Control.TitleLabel.LineBreakMode = UILineBreakMode.WordWrap;
Control.TitleLabel.TextAlignment = textAlignment;
}
}
}
Then use it within XAML:
<controls:MultilineButton Text="This Button is Centered!" HorizontalTextAlignment="Center" />

Resources