I know the default DatePicker already has a bottom line, but I'm trying to add a bottom line to the DatePicker in the custom renderer code (for some purpose).
I can set a full border of my GradientDrawable object by myGradientDrawable.SetStroke(3, myColor); but I don't know how to add only the bottom line so anyone can help me please?
Try this:
public class CustomPickerRenderer : PickerRenderer
{
public CustomPickerRenderer(Context context) : base(context)
{
}
private AlertDialog alert;
private CustomPicker element;
private int selectedIndex;
public LayerDrawable AddPickerStyles(string imagePath)
{
ColorDrawable borderColorDrawable = new ColorDrawable(Xamarin.Forms.Color.FromHex("#43addf").ToAndroid());
ColorDrawable backgroundColorDrawable = new ColorDrawable(Xamarin.Forms.Color.FromHex("#7e1b80").ToAndroid());
Drawable[] drawables = new Drawable[]
{
borderColorDrawable, backgroundColorDrawable
};
LayerDrawable layerDrawable = new LayerDrawable(drawables);
layerDrawable.SetLayerInset(1, 0, 0, 0, 5);
return layerDrawable;
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
element = (CustomPicker)this.Element;
if (Control != null && this.Element != null)
{
Control.Background = AddPickerStyles(element.Image);
}
}
}
Related
I was creating a custom control it is more like TabPage, where it's derive from View, containing a list of CSMenuItems and foreach menuItem is derived from BaseMenuItem and has menuContent that is derived from ContentView, like this :
• CSView
• CSMenuItem
• MenuContent
• Content
• CSMenuItem
• MenuContent
• Content
My problem is whenever I Change the properties value of MenuContent in the xaml file, the propertyChanged won't fire and MenuContent won't update. I am pretty sure the problem is in my renderers. Is there any way to update the child element in custom renderer?
Here are my codes for controls:
class CSView : View
{
public CSView ()
{
var items = new ObservableCollection <CSMenuItem>();
items.CollectionChanged += OnItemsChanged;
Items = items;
}
public static readonly BindableProperty ItemsProperty = BindableProperty.Create ("Items", typeof (IList <CSMenuItem>), typeof (CSView));
void OnItemsChanged (object sender, NotifyCollectionChangedEventArgs e)
{
foreach (CSMenuItem item in e.NewItems)
item.Parent = this;
// Maybe setting the item parent to this would be good?
// cause whenever new item is add I want its parent to be this.
// Correct me if im wrong.
}
public IList <CSMenuItem> Items
{
get => (IList <CSMenuItem>)GetValue (ItemsProperty);
set => SetValue (ItemsProperty, value);
}
class CSMenuItem : BaseMenuItem
{
public CSMenuItem()
{
}
public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(CSMenuItem), default);
public static readonly BindableProperty ContentProperty = BindableProperty.Create("Content", typeof(MenuContent), typeof(CSMenuItem));
public string Title
{
get => (string)GetValue (TitleProperty);
set => SetValue (TitleProperty, value);
}
public MenuContent Content
{
get => (MenuContent)GetValue (ContentProperty);
set => SetValue (ContentProperty, value);
}
}
class MenuContent : ContentView
{
public MenuContent ()
{
}
public static readonly BindableProperty TitleProperty = BindableProperty.Create ("Title", typeof (string), typeof (MenuContent));
public string Title
{
get => (string)GetValue (TitleProperty);
set => SetValue (TitleProperty, value);
}
}
and for renderers :
class CSViewRenderer : ViewRenderer<CSView, FrameLayout>, NavigationBarView.IOnItemSelectedListener
{
...
protected override void OnElementChanged (ElementChangedEventArgs <CSView> e)
{
if (e.OldElement != null)
UnhandleEvents (e.OldElement);
if (e.NewElement != null)
{
if (Control == null)
SetNativeControl (_control);
HandleEvents (e.NewElement)
}
}
void HandleEvents (CSView element)
=> element.PropertyChanged += OnElementPropertyChanged;
void UnhandleEvents (CSView element)
=> element.PropertyChanged -= OnElementPropertyChanged;
}
/// this is where the issue came from
class CSMenuItemRender : AbsItemRenderer
{
...
protected override View CreateNativeControl ()
{
_base = new LinearLayout (Context);
_toolBar = new ToolBar (Context)
_content = Platform.CreateRendererWithContext (Element.Content, Context); // where Element is type of MenuItem
_toolBar.Title = Element.Content.Title;
_base.Orientation = Orientation.Vertical;
_base.AddView (_toolBar, new LinearLayout.LayoutParams (-1, -2);
_base.AddView (_content.View, new LinearLayout.LayoutParams (-1, -2);
ElementRendererUtil.FitElement (Context, _base, _content);
Platform.SetRenderer (Element.Content, _content);
_content.ElementChanged += OnRendererElementChanged;
return _base;
}
void OnElementChanged (ElementChangedEventArgs <CSMenuItem> e)
{
if (e.OldElement != null)
UnhandleEvents (e.OldElement);
if (e.NewElement != null)
{
if (Control == null)
SetNativeControl (this)
HandleEvents (e.NewElement);
}
}
void OnRendererElementChanged (object sender, VisualElementChangedEventArgs e)
{
if (e.OldElement != null)
e.OldElement.PropertyChanged -= OnRendererElementPropertyChanged;
if (e.NewElement != null)
e.NewElement.PropertyChanged += OnRendererElementPropertyChanged;
}
void OnRendererElementPropertyChanged (object sender, PropertyChangedEventArgs e)
{
var menuContent = (MenuContent)sender;
if (e.PropertyName == MenuContent.TitleProperty.PropertyName)
_toolBar.Title = menuContent.Title;
else if (e.PropertyName == MenuContent.ContentProperty.PropertyName)
_content.Tracker.UpdateLayout ();
}
void HandleEvents (CSMenuItem element)
=> element.PropertyChanged += OnElementPropertyChanged;
void UnhandleEvents (CSMenuItem element)
=> element.PropertyChanged -= OnElementPropertyChanged;
}
I was able to change the MenuContent properties like Title when changing the derive type to Element, like this:
class MenuContent : Element
And setting its parent to CSMenuItem, like this :
public MenuContent Content
{
...
set {
if (value.Parent != this)
value.Parent = this;
SetValue (ContentProperty, value);
}
}
but still the Content property of MenuContent won't update when the value is changed. What I want is to update the MenuContent what ever the value changed in xaml file and to know why the propertyChanged won't fire.
Sorry for my bad English, hope you understand. May the Almighty Bless You😇 Stay safe.
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.
I have an Entry in Xamarin Forms and for Android, its displayed with an underline by default and when I test it for iOS it's always with a rectangle like this:
And I want it to be like just like in Android:
I used Custom render where I specified that I want an underline but its still displayed in a rectangle. This code doesn't work:
class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.Line;
}
}
}
I have solved this problem with the next code:
class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.None;
//Create borders(bottom only)
CALayer border = new CALayer();
float width = 2.0f;
border.BorderColor = Color.FromHex(ColorConstants.BlueHex).ToCGColor();
border.Frame = new CGRect( 0, 40, 400, 2.0f);
border.BorderWidth = width;
Control.Layer.AddSublayer(border);
Control.Layer.MasksToBounds = true;
}
}
}
But still, don't understand why the previous code doesn't work, it should be simple as that. Any ideas?
https://developer.xamarin.com/api/type/UIKit.UITextBorderStyle/
The enum values for UITextBorderStyle are Bezel, Line, None, and RoundedRect. All of them (except for None) simply describe the style of the border that goes around the entire UITextView, so the Line value doesn't mean a single line, it means a solid line rectangle around the entire view, as opposed to a Bezel or rounded rectangle.
the implementation witch has solved the user question didn't work for me in these days, but this one has worked: https://social.msdn.microsoft.com/Forums/en-US/7ae2e5ad-3cc4-4761-ac39-06e8d8842cf4/entry-bottom-line?forum=xamarinforms
as jmoerdyk gently suggested this link explains how to write a custom renderer for iOS to render an entry with a bottom line instead of a complete border. the color of the line will change when the entry has focus.
below the piece of code with some null checks:
public class IOSBottomLineEntryRenderer : EntryRenderer
{
private CALayer _borderLayer;
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
Control.BorderStyle = UITextBorderStyle.None;
if (Element == null)
return;
DrawBorder(UIColor.FromRGB(156, 156, 156));
e.NewElement.Unfocused += (_, _) =>
{
DrawBorder(UIColor.FromRGB(156, 156, 156));
};
e.NewElement.Focused += (_, _) =>
{
DrawBorder(UIColor.FromRGB(245, 0, 47));
};
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null)
return;
if (Element == null)
return;
DrawBorder(UIColor.FromRGB(156, 156, 156));
}
public override CGRect Frame
{
get { return base.Frame; }
set
{
base.Frame = value;
if (Control == null)
return;
if (Element == null)
return;
DrawBorder(UIColor.FromRGB(156, 156, 156));
}
}
private void DrawBorder(UIColor borderColor)
{
if (Frame.Height <= 0 || Frame.Width <= 0)
return;
if (_borderLayer == null)
{
_borderLayer = new CALayer
{
MasksToBounds = true,
Frame = new CGRect(0f, Frame.Height - 1, Frame.Width, 1f),
BorderColor = borderColor.CGColor,
BorderWidth = 1.0f
};
Control.Layer.AddSublayer(_borderLayer);
Control.BorderStyle = UITextBorderStyle.None;
}
else
{
_borderLayer.BorderColor = borderColor.CGColor;
_borderLayer.Frame = new CGRect(0f, Frame.Height - 1, Frame.Width, 1f);
}
}
}
I was wondering if someone can point out how I can change a single entry?. I've made a custom renderer which changes the border of an entry to red but what I really want is only to change one entry if validation fails from black to red.
Picture of entries:
My renderer:
[assembly: ExportRenderer(typeof(App.RedFrameEntry), typeof(RedFrameEntryRenderer))]
namespace App.iOS
{
public class RedFrameEntryRenderer : EntryRenderer
{
public bool isInvalid = false;
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.RoundedRect;
Control.Layer.CornerRadius = 4;
Control.Layer.BorderColor = Color.FromHex("#c60303").ToCGColor();
Control.Layer.BorderWidth = 0;
if (isInvalid)
{
Control.Layer.BorderWidth = 2;
}
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
}
}
And my code:
private void ChangeEntryOnValidationFail(string text, Entry entry, int numberOfChar)
{
if (String.IsNullOrEmpty(text) || text.Length < numberOfChar)
{
// TODO: Change to RedFrameEntry
}
else
{
// TODO: Change back to default
}
}
I'd suggest you create a CustomEntry class which has a base class of Entry, so instead of changing the border of all entries, you can just call the CustomEntry whenever you need it.
public class CustomEntry : Entry{
}
then use:
[assembly: ExportRenderer(typeof(CustomEntry), typeof(RedFrameEntryRenderer))]
Hope it helps!
I'm looking for a way to resize a TableColumn in a TableView so that all of the content is visible in each cell (i.e. no truncation).
I noticed that double clicking on the column divider's does auto fit the column to the contents of its cells. Is there a way to trigger this programmatically?
Digging through the javafx source, I found that the actual method called when you click TableView columns divider is
/*
* FIXME: Naive implementation ahead
* Attempts to resize column based on the pref width of all items contained
* in this column. This can be potentially very expensive if the number of
* rows is large.
*/
#Override protected void resizeColumnToFitContent(TableColumn<T, ?> tc, int maxRows) {
if (!tc.isResizable()) return;
// final TableColumn<T, ?> col = tc;
List<?> items = itemsProperty().get();
if (items == null || items.isEmpty()) return;
Callback/*<TableColumn<T, ?>, TableCell<T,?>>*/ cellFactory = tc.getCellFactory();
if (cellFactory == null) return;
TableCell<T,?> cell = (TableCell<T, ?>) cellFactory.call(tc);
if (cell == null) return;
// set this property to tell the TableCell we want to know its actual
// preferred width, not the width of the associated TableColumnBase
cell.getProperties().put(TableCellSkin.DEFER_TO_PARENT_PREF_WIDTH, Boolean.TRUE);
// determine cell padding
double padding = 10;
Node n = cell.getSkin() == null ? null : cell.getSkin().getNode();
if (n instanceof Region) {
Region r = (Region) n;
padding = r.snappedLeftInset() + r.snappedRightInset();
}
int rows = maxRows == -1 ? items.size() : Math.min(items.size(), maxRows);
double maxWidth = 0;
for (int row = 0; row < rows; row++) {
cell.updateTableColumn(tc);
cell.updateTableView(tableView);
cell.updateIndex(row);
if ((cell.getText() != null && !cell.getText().isEmpty()) || cell.getGraphic() != null) {
getChildren().add(cell);
cell.applyCss();
maxWidth = Math.max(maxWidth, cell.prefWidth(-1));
getChildren().remove(cell);
}
}
// dispose of the cell to prevent it retaining listeners (see RT-31015)
cell.updateIndex(-1);
// RT-36855 - take into account the column header text / graphic widths.
// Magic 10 is to allow for sort arrow to appear without text truncation.
TableColumnHeader header = getTableHeaderRow().getColumnHeaderFor(tc);
double headerTextWidth = Utils.computeTextWidth(header.label.getFont(), tc.getText(), -1);
Node graphic = header.label.getGraphic();
double headerGraphicWidth = graphic == null ? 0 : graphic.prefWidth(-1) + header.label.getGraphicTextGap();
double headerWidth = headerTextWidth + headerGraphicWidth + 10 + header.snappedLeftInset() + header.snappedRightInset();
maxWidth = Math.max(maxWidth, headerWidth);
// RT-23486
maxWidth += padding;
if(tableView.getColumnResizePolicy() == TableView.CONSTRAINED_RESIZE_POLICY) {
maxWidth = Math.max(maxWidth, tc.getWidth());
}
tc.impl_setWidth(maxWidth);
}
It's declared in
com.sun.javafx.scene.control.skin.TableViewSkinBase
method signature
protected abstract void resizeColumnToFitContent(TC tc, int maxRows)
Since it's protected, You cannot call it from e.g. tableView.getSkin(), but You can always extend the TableViewSkin overriding only resizeColumnToFitContent method and make it public.
As #Tomasz suggestion, I resolve by reflection:
import com.sun.javafx.scene.control.skin.TableViewSkin;
import javafx.scene.control.Skin;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class GUIUtils {
private static Method columnToFitMethod;
static {
try {
columnToFitMethod = TableViewSkin.class.getDeclaredMethod("resizeColumnToFitContent", TableColumn.class, int.class);
columnToFitMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public static void autoFitTable(TableView tableView) {
tableView.getItems().addListener(new ListChangeListener<Object>() {
#Override
public void onChanged(Change<?> c) {
for (Object column : tableView.getColumns()) {
try {
columnToFitMethod.invoke(tableView.getSkin(), column, -1);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
});
}
}
The current versions of JavaFX (e.g. 15-ea+1) resize table columns, if the prefWidth was never set (or is set to 80.0F, see TableColumnHeader enter link description here).
In Java 16 we can extend TableView to use a custom TableViewSkin which in turn uses a custom TableColumnHeader
class FitWidthTableView<T> extends TableView<T> {
public FitWidthTableView() {
setSkin(new FitWidthTableViewSkin<>(this));
}
public void resizeColumnsToFitContent() {
Skin<?> skin = getSkin();
if (skin instanceof FitWidthTableViewSkin<?> tvs) tvs.resizeColumnsToFitContent();
}
}
class FitWidthTableViewSkin<T> extends TableViewSkin<T> {
public FitWidthTableViewSkin(TableView<T> tableView) {
super(tableView);
}
#Override
protected TableHeaderRow createTableHeaderRow() {
return new TableHeaderRow(this) {
#Override
protected NestedTableColumnHeader createRootHeader() {
return new NestedTableColumnHeader(null) {
#Override
protected TableColumnHeader createTableColumnHeader(TableColumnBase col) {
return new FitWidthTableColumnHeader(col);
}
};
}
};
}
public void resizeColumnsToFitContent() {
for (TableColumnHeader columnHeader : getTableHeaderRow().getRootHeader().getColumnHeaders()) {
if (columnHeader instanceof FitWidthTableColumnHeader colHead) colHead.resizeColumnToFitContent(-1);
}
}
}
class FitWidthTableColumnHeader extends TableColumnHeader {
public FitWidthTableColumnHeader(TableColumnBase col) {
super(col);
}
#Override
public void resizeColumnToFitContent(int rows) {
super.resizeColumnToFitContent(-1);
}
}
You can use tableView.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
You can also try switching between the two policies TableView.CONSTRAINED_RESIZE_POLICY
and TableView.UNCONSTRAINED_RESIZE_POLICY in case TableView.UNCONSTRAINED_RESIZE_POLICY alone doesn't fit your need.
Here's a useful link.