Xamarin Forms Custom View Renderer for TextView Height issue - xamarin.forms

I want to render xamarin forms view control into android textview.
Below customrenderer renders Android TextView on Android device with initial default text.
Now, If I add more text into it, this control would not grow in height but instead becomes scrollable to see the text not visible on screen.
On other way around, if I reduce text content, this control would not reduce in height and occupy the same space and show reduced text content on scree.
Issue : In this case of text change, this label should increase or reduce height on screen for Android. It is working fine on IOS as expected.
BindingText is bindable property to set text for this control.
CopyableLabelRenderer is my custom renderer class for Xamarin Forms
view.
Here is a sample code.
class CopyableLabelRenderer : ViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
try
{
var label = new TextView(MainApplication.Context);
label.SetTextIsSelectable(true);
label.LinksClickable = true;
label.Text = (e.NewElement as CopyableLabel).BindingText; // this line sets text into this label
SetNativeControl(label);
}
catch (Exception ex)
{
var msg = ex.Message;
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName.Equals("BindingText"))
{
TextView textView = this.Control as TextView;
textView.Text = (Element as CopyableLabel).BindingText;
}
}
}
Here is a base class,
public class CustomLabel : View
{
public static readonly BindableProperty BindingTextProperty = BindableProperty.Create("BindingText", typeof(string),typeof(View),
string.Empty
);
public string BindingText
{
get
{
return (string)GetValue(BindingTextProperty);
}
set
{
SetValue(BindingTextProperty, value);
}
}
}

Related

custom LabelRenderer shows wrong text style inside list view on scroll on Xamarin iOS

I am working on an App that requires one list view having text labels with NSUnderlineStyle on user deletion.
As per the requirement user have Delete/restore option in detail screen. On delete confirmation the text label should be underline style for that particular cell.
I am using LabelRenderer for NSUnderlineStyle in Xamarin iOS.
But currently ListView displays text Labels with underline style which is not deleted by user on list view scroll. The underline style are swapping from one cell label to another on list view scroll.
Below my sample code.
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (this.Control == null)
{
return;
}
if (this.Element is ExtendedLabel extended)
{
var strikethroughStyle = extended.IsStrikeThrough ? NSUnderlineStyle.Single : NSUnderlineStyle.None;
this.Control.AttributedText = new NSMutableAttributedString(
extended.Text ?? string.Empty,
this.Control.Font,
strikethroughStyle: strikethroughStyle);
}
}
This is the common issue of TableView Cell Resue , tableView will reuse the cell for the visible(inside screen shot) ones , so it would show the previous style .
To solve this we can forcedly set the style each time when the cell is going to display .
Create custom renderer for ListView , and do this in WillDisplay method ,before it we need to override TableView's Delegate.
[assembly: ExportRenderer(typeof(ListView), typeof(MyRenderer))]
namespace FormsApp.iOS
{
public class MyDelegate : UITableViewDelegate
{
List<Model> data;
public MyDelegate(List<Model> _data)
{
data = _data;
}
public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
{
var views = cell.ContentView.Subviews;
foreach (var view in views)
{
if(view is LabelRenderer renderer)
{
UILabel label = renderer.Control;
var strikethroughStyle = data[indexPath.Row].YourProperty?NSUnderlineStyle.Single : NSUnderlineStyle.None;
label.AttributedText = new NSMutableAttributedString(
label.Text ?? string.Empty,
label.Font,
strikethroughStyle: strikethroughStyle);
}
}
}
}
public class MyRenderer : ListViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
// Unsubscribe
}
if (e.NewElement != null)
{
IEnumerable<Model> data = (IEnumerable<Model>)Element.ItemsSource;
Control.Delegate = new MyDelegate(data.ToList());
}
}
}
}

C#, Xamarin Forms: No Custom TextChangedEvent Raised on initialization

I'm creating an Xamarin.Forms MVVM App (only using Android) which needs certain buttons to be outlined red, whenever their text property holds a specific value. (Purpose: alert the user to press the button and select a value, which will change the Button Text Property and therefore remove the red outline)
To achieve this I've create the following documents:
A custom button CButton that extents the default Button:
public class CButton : Button
{
// this Hides the Default .Text-Property
public string Text
{
get => base.Text;
set
{
base.Text = value;
TextChangedEvent(this, new EventArgs());
}
}
// The Raised Event
protected virtual void TextChangedEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handler = TextChanged;
handler(sender, e);
}
public event EventHandler<EventArgs> TextChanged;
}
A custom behavior makes use of the raised TextChangedEvent
public class ButtonValBehavior : Behavior<CButton>
{
protected override void OnAttachedTo(CButton bindable)
{
bindable.TextChanged += HandleTextChanged;
base.OnAttachedTo(bindable);
}
void HandleTextChanged(object sender, EventArgs e)
{
string forbidden = "hh:mm|dd.mm.yyyy";
if (forbidden.Contains((sender as CButton).Text.ToLower()))
{
//Do when Button Text = "hh:mm" || "dd.mm.yyyy"
(sender as CButton).BorderColor = Color.Gray;
}
else
{
//Do whenever Button.Text is any other value
(sender as CButton).BorderColor = Color.FromHex("#d10f32");
}
}
protected override void OnDetachingFrom(CButton bindable)
{
bindable.TextChanged -= HandleTextChanged;
base.OnDetachingFrom(bindable);
}
}
The relevant parts of the ViewModel look the following:
public class VM_DIVI : VM_Base
{
public VM_DIVI(O_BasisProtokoll base)
{
Base = base;
}
private O_BasisProtokoll _base = null;
public O_BasisProtokoll Base
{
get => _base;
set
{
_base = value;
OnPropertyChanged();
}
}
Command _datePopCommand;
public Command DatePopCommand
{
get
{
return _datePopCommand ?? (_datePopCommand = new Command(param => ExecuteDatePopCommand(param)));
}
}
void ExecuteDatePopCommand(object param)
{
//launch popup
var p = new PP_DatePicker(param);
PopupNavigation.Instance.PushAsync(p);
}
}
The .xmal looks the following (b is the xmlns of the Namespace):
<b:CButton x:Name="BTN_ED_Datum"
Text="{Binding Base.ED_datum, Mode=TwoWay}"
Grid.Column="1"
Command="{Binding DatePopCommand}"
CommandParameter="{x:Reference BTN_ED_Datum}">
<b:CButton.Behaviors>
<b:ButtonValBehavior/>
</b:CButton.Behaviors>
</b:CButton>
This solution works fine whenever the input is caused by user interaction. However, when a Value is assigned during the initialization of the Page no red outline is created, in fact the TextChangedEvent isn't raised. By using breakpoints I noticed that during initialization the Text Property of CButton is never set, eventhough it actually will be in the view.
Despite fiddling around with my solution I cannot make this work on initialization. I tried to work around this issue by outlining every button by default in their constructor, however this will outline every button red, even when their text value doesn't require them to be.
How can I achieve my initial goal?
Many thanks in advance!
It's been a while but if I recall correctly what I ended up doing was:
Changing the new Text-Property of my custom Button to CText and
Making sure that I have Mode=TwoWay activated for any Element, that doesn't have it enabled by default. (Look up Binding modes on msdn for more)
making CText a bindable property of CButton
My custom button now looks the following:
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
namespace EORG_Anton.Model
{
public class CButton : Button
{
public static readonly BindableProperty CTextProperty =
BindableProperty.Create(nameof(CText),
typeof(string),
typeof(CButton),
default(string),
BindingMode.TwoWay,
propertyChanged: OnTextChanged);
private static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (CButton)bindable;
var value = (string)newValue;
control.CText = value;
}
public string CText
{
get => base.Text;
set
{
base.Text = value;
TextChangedEvent(this, new EventArgs());
}
}
protected virtual void TextChangedEvent(object sender, EventArgs e)
{
EventHandler<EventArgs> handler = TextChanged;
handler(sender, e);
}
public event EventHandler<EventArgs> TextChanged;
}
}

Xamarin Forms ListView in ScrollView - Android disable scrolling in listview

I am facing problem when using a ListView inside a Scrollview in Android (via Xamarin Forms).
I am calculating the height of the listview based on item source and a row height but i can still scroll inside the listview :
The listview is the white area contained in the scrollview, with few labels below, so we can scroll :
But in Android we can also scroll inside the listview itself :
I tried to catch the move event in a custom renderer :
public class CustomListViewRenderer : ListViewRenderer
{
public CustomListViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
var customListView = Element as CustomListView;
if (customListView == null)
{
return;
}
if (customListView.IsScrollingEnable == false)
{
Control.Touch += ListView_Touch;
}
}
private void ListView_Touch(object sender, TouchEventArgs e)
{
if (e.Event.Action == MotionEventActions.Move)
{
e.Handled = true;
}
else
{
e.Handled = false;
}
}
}
This can solve the space issue but if the user scroll from listview area with touch, the global scrollview doesn't work (you can only scroll with touch from Aqua Blue area).
Is there a way to disable the scroll in the listview without affect the parent scrollview ? The link to the sample project to reproduce the issue : https://1drv.ms/u/s!An8JKHwJo47up2ss_6pdzlXOhtDz?e=GdXrbf
Like Jason said, you could use the Listview directly.
With your code, you could set setScrollContainer to false in custom renderer.
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.ListView> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
return;
if (Control != null)
{
Control.SetScrollContainer(false);
}
}

Xamarin Forms - UWP custom renderer: how to add a child to a stacklayout

I'm trying to insert a UWP specific child in the custom renderer of a StackLayout.
However, in the sample code below, Control is always null whereas my StackLayout has Children. Maybe StackPanel is not what StackLayout is rendered into in UWP.
public class MyRenderer : ViewRenderer<StackLayout, StackPanel>
{
private bool _childAdded;
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (!_childAdded && Control?.Children != null)
{
_childAdded = true;
Control.Children.Insert(0, new Windows.UI.Xaml.Shapes.Rectangle());
}
base.OnElementPropertyChanged(sender, e);
}
}
Some modification in you are cade because you are calling base.OnElementPropertyChanged(sender,e) after code implementation. Just try to use below code.
public class MyRenderer : ViewRenderer<StackLayout, StackPanel>
{
private bool _childAdded;
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if(Control==null)
return;
if (!_childAdded && Control.Children != null)
{
_childAdded = true;
Control.Children.Insert(0, new Windows.UI.Xaml.Shapes.Rectangle());
}
}
}
The StackLayout (Layout) renderer is ViewRenderer and implemented on UWP by FrameworkElement; Renderer Base Classes and Native Controls.
Theoretical renderer:
public class MyRenderer : ViewRenderer<StackLayout, FrameworkElement>
...
Control is always null whereas my StackLayout has Children. Maybe StackPanel.
Derive from official document,
In Xamarin.Forms, all layout classes derive from the Layout<T> class and constrain the generic type to View and its derived types. But the layout of children element is incorrect.
And the match Native control within UWP platform is LayoutRenderer. So it is not inherit StackPanel directly. You could also custom a customrederer like the follow.
[assembly: ExportRenderer(typeof(StackLayout), typeof(ICustomStackLayoutRenderer))]
namespace CustomStackLayoutRenderer.UWP
{
public class ICustomStackLayoutRenderer : ViewRenderer<StackLayout, StackPanel>
{
private bool _childAdded;
protected override void OnElementChanged(ElementChangedEventArgs<StackLayout> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var stacklayout = new StackPanel();
SetNativeControl(stacklayout);
}
if (e.OldElement != null)
{
}
if (e.NewElement != null)
{
if (!_childAdded && Control.Children != null)
{
_childAdded = true;
Control.Children.Insert(0, new Windows.UI.Xaml.Shapes.Rectangle() { Width = 100, Height = 100, Fill = new SolidColorBrush(Colors.Red) });
}
}
}
}
}
For your requirement, the better way is that create a CustomStackLayout inherit StackLayout in Xamarin.Forms, and re-layout your children element in your LayoutChildren override method. For more detail you could refer Creating a Custom Layout.

How to make Xamarin.Forms.Editor scrollable/auto resizable?

I have an scrollabe layout with an Editor inside that I'd like to make scrollable or auto resizable to fit the contents.
I can't find how to do it.
I tried a custom renderer but I can't find how to set InputMethods to the Control.
Any ideas?
With the help of this post: https://forums.xamarin.com/discussion/80360/editor-inside-scrollview-not-scrolling
That fixed the scrolling on Android (iOS works by default). It avoids the parent scroll event when touching inside the Editor, triggering only the Editor scroll.
First a class on Android project:
using Android.Views;
namespace MyApp.Droid
{
public class DroidTouchListener : Java.Lang.Object, View.IOnTouchListener
{
public bool OnTouch(View v, MotionEvent e)
{
v.Parent?.RequestDisallowInterceptTouchEvent(true);
if ((e.Action & MotionEventActions.Up) != 0 && (e.ActionMasked & MotionEventActions.Up) != 0)
{
v.Parent?.RequestDisallowInterceptTouchEvent(false);
}
return false;
}
}
}
And then use it on the Android Custom EditorRenderer:
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
var nativeEditText = (global::Android.Widget.EditText)Control;
//While scrolling inside Editor stop scrolling parent view.
nativeEditText.OverScrollMode = OverScrollMode.Always;
nativeEditText.ScrollBarStyle = ScrollbarStyles.InsideInset;
nativeEditText.SetOnTouchListener(new DroidTouchListener());
//For Scrolling in Editor innner area
Control.VerticalScrollBarEnabled = true;
Control.MovementMethod = ScrollingMovementMethod.Instance;
Control.ScrollBarStyle = Android.Views.ScrollbarStyles.InsideInset;
//Force scrollbars to be displayed
Android.Content.Res.TypedArray a = Control.Context.Theme.ObtainStyledAttributes(new int[0]);
InitializeScrollbars(a);
a.Recycle();
}
}

Resources