J'ai ce XAML. Ce que je voudrais faire est de mettre une ligne 1px en haut et en bas de la grille avec un rendu iOS. Quelqu'un peut-il me dire qu'il existe un moyen spécial de placer une ligne de bordure en haut et en bas d'une grille à l'aide d'un moteur de rendu?
<Grid x:Name="phraseGrid" BackgroundColor="Transparent"
Margin="0,55,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="10*" />
<RowDefinition Height="6*" />
<RowDefinition Height="80*" />
<RowDefinition Height="13*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid x:Name="prGrid" Grid.Row="0" Grid.Column="0"
Padding="5,0,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
BackgroundColor="#EEEEEE">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25*" />
<ColumnDefinition Width="25*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<Label x:Name="cards" Style="{StaticResource smallLabel}" Grid.Row="0" Grid.Column="0" />
<Label x:Name="points" Style="{StaticResource smallLabel}" Grid.Row="0" Grid.Column="1" />
<Label x:Name="timer" Style="{StaticResource smallLabel}" Grid.Row="0" Grid.Column="2" />
</Grid>
Du point de vue de la maintenabilité et de la complexité, je vous recommande de créer quelques propriétés pouvant être liées et de les utiliser pour le rendu des bordures.
Trois options sont disponibles pour implémenter ceci:
1. Rendu de la plate-forme : Étend Grid
avec des propriétés et dessine des frontières au niveau de la plate-forme.
2. Contrôle des formulaires : Utilisez Padding
et BackgroundColor
pour donner l’apparence d’une bordure.
3. Platform-effect : Créez un PlatformEffect
pour le rendu de la bordure (dans ce cas, nous définissons des propriétés attachables liées), et attachez-le à tout élément visuel.
Vous pouvez étendre Grid
pour créer un contrôle personnalisé et implémenter le rendu correspondant. Cet exemple de code montre comment implémenter cette approche à l'aide d'une approche de contrôle personnalisé.
Implémentation du contrôle personnalisé:
public class ExtendedGrid : Grid
{
/// <summary>
/// The border color property.
/// </summary>
public static readonly BindableProperty BorderColorProperty =
BindableProperty.Create(
"BorderColor", typeof(Color), typeof(ExtendedGrid),
defaultValue: Color.Black);
/// <summary>
/// Gets or sets the color of the border.
/// </summary>
/// <value>The color of the border.</value>
public Color BorderColor
{
get { return (Color)GetValue(BorderColorProperty); }
set { SetValue(BorderColorProperty, value); }
}
/// <summary>
/// The border width property.
/// </summary>
public static readonly BindableProperty BorderWidthProperty =
BindableProperty.Create(
"BorderWidth", typeof(Thickness), typeof(ExtendedGrid),
defaultValue: new Thickness(1));
/// <summary>
/// Gets or sets the width of the border.
/// </summary>
/// <value>The width of the border.</value>
public Thickness BorderWidth
{
get { return (Thickness)GetValue(BorderWidthProperty); }
set { SetValue(BorderWidthProperty, value); }
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if(nameof(Padding).Equals(propertyName) || nameof(BorderWidth).Equals(propertyName))
{
double minLeft, minRight, minTop, minBottom;
// ensure padding is always greater than borderwidth - we will have overlapping issue with client-area
minLeft = Math.Max(Padding.Left, BorderWidth.Left);
minRight = Math.Max(Padding.Right, BorderWidth.Right);
minTop = Math.Max(Padding.Top, BorderWidth.Top);
minBottom = Math.Max(Padding.Bottom, BorderWidth.Bottom);
var minPadding = new Thickness(minLeft, minTop, minRight, minBottom);
if (!minPadding.Equals(Padding)) //add this check to ensure we don't end up in a recursive loop
Padding = minPadding;
}
}
}
Et, le rendu peut être implémenté comme:
[Assembly: ExportRenderer(typeof(ExtendedGrid), typeof(ExtendedGridRenderer))]
namespace AppNamespace.iOS
{
public class ExtendedGridRenderer : VisualElementRenderer<ExtendedGrid>
{
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
//redraw border if any of these properties changed
if (e.PropertyName == VisualElement.WidthProperty.PropertyName ||
e.PropertyName == VisualElement.HeightProperty.PropertyName ||
e.PropertyName == ExtendedGrid.BorderWidthProperty.PropertyName ||
e.PropertyName == ExtendedGrid.BorderColorProperty.PropertyName)
SetNeedsDisplay();
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
var box = Element;
if (box == null)
return;
RemoveBorderLayers(); //remove previous layers - this can further be optimized.
CGColor lineColor = box.BorderColor.ToCGColor();
nfloat leftBorderWidth = new nfloat(box.BorderWidth.Left);
nfloat rightBorderWidth = new nfloat(box.BorderWidth.Right);
nfloat topBorderWidth = new nfloat(box.BorderWidth.Top);
nfloat bottomBorderWidth = new nfloat(box.BorderWidth.Bottom);
if(box.BorderWidth.Left > 0)
{
var leftBorderLayer = new BorderCALayer();
leftBorderLayer.BackgroundColor = lineColor;
leftBorderLayer.Frame = new CGRect(0, 0, leftBorderWidth, box.Height);
InsertBorderLayer(leftBorderLayer);
}
if (box.BorderWidth.Right > 0)
{
var rightBorderLayer = new BorderCALayer();
rightBorderLayer.BackgroundColor = lineColor;
rightBorderLayer.Frame = new CGRect(box.Width - box.BorderWidth.Right, 0, rightBorderWidth, box.Height);
InsertBorderLayer(rightBorderLayer);
}
if (box.BorderWidth.Top > 0)
{
var topBorderLayer = new BorderCALayer();
topBorderLayer.BackgroundColor = lineColor;
topBorderLayer.Frame = new CGRect(0, 0, box.Width, topBorderWidth);
InsertBorderLayer(topBorderLayer);
}
if (box.BorderWidth.Bottom > 0)
{
var bottomBorderLayer = new BorderCALayer();
bottomBorderLayer.BackgroundColor = lineColor;
bottomBorderLayer.Frame = new CGRect(0, box.Height - box.BorderWidth.Bottom, box.Width, bottomBorderWidth);
InsertBorderLayer(bottomBorderLayer);
}
}
void RemoveBorderLayers()
{
if (NativeView.Layer.Sublayers?.Length > 0)
{
var layers = NativeView.Layer.Sublayers.OfType<BorderCALayer>();
foreach(var layer in layers)
layer.RemoveFromSuperLayer();
}
}
void InsertBorderLayer(BorderCALayer layer)
{
var index = (NativeView.Layer.Sublayers?.Length > 0) ? NativeView.Layer.Sublayers.Length - 1 : 0;
//This is needed to get every background redrawn if the color changes on runtime
NativeView.Layer.InsertSublayer(layer, index);
}
}
public class BorderCALayer : CoreAnimation.CALayer { } //just create a type for easier replacement
}
Exemple d'utilisation et de sortie:
<Grid Margin="20">
<Grid x:Name="phraseGrid" BackgroundColor="Transparent"
Margin="0,55,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="10*" />
<RowDefinition Height="6*" />
<RowDefinition Height="80*" />
<RowDefinition Height="13*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<local:ExtendedGrid x:Name="prGrid1" Grid.Row="0" Grid.Column="0"
Padding="5,0,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
BackgroundColor="#EEEEEE"
BorderColor="Gray"
BorderWidth="0,2,0,2">
<Label Text="only top and bottom set" Grid.Row="0" Grid.Column="0" />
</local:ExtendedGrid>
<local:ExtendedGrid x:Name="prGrid2" Grid.Row="1" Grid.Column="0"
Padding="5,0,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
BackgroundColor="Gray"
BorderColor="Blue"
BorderWidth="2">
<Label Text="all border set" Grid.Row="0" Grid.Column="0" />
</local:ExtendedGrid>
<local:ExtendedGrid x:Name="prGrid3" Grid.Row="2" Grid.Column="0"
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
BackgroundColor="Silver"
BorderColor="Red"
BorderWidth="0,2,0,2">
<Label Text="no horizontal borders" Grid.Row="0" Grid.Column="0" />
</local:ExtendedGrid>
</Grid>
</Grid>
Si vous ne voulez pas avoir à vous soucier de la mise en œuvre des moteurs de rendu pour chaque plate-forme, vous pouvez également créer un contrôle personnalisé BorderView
en tant que wrapper pour rendre la bordure au niveau du formulaire lui-même (à l'aide d'un simple Padding
et BackgroundColor
hack). plates-formes. L'inconvénient est qu'il introduit une vue wrapper supplémentaire pour ajouter une bordure et que la vue enfant ne peut pas avoir un arrière-plan transparent.
Implémentation BorderView:
public class BorderView : ContentView
{
/// <summary>
/// The border color property.
/// </summary>
public static readonly BindableProperty BorderColorProperty =
BindableProperty.Create(
"BorderColor", typeof(Color), typeof(BorderView),
defaultValue: Color.Black);
/// <summary>
/// Gets or sets the color of the border.
/// </summary>
/// <value>The color of the border.</value>
public Color BorderColor
{
get { return (Color)GetValue(BorderColorProperty); }
set { SetValue(BorderColorProperty, value); }
}
/// <summary>
/// The border width property.
/// </summary>
public static readonly BindableProperty BorderWidthProperty =
BindableProperty.Create(
"BorderWidth", typeof(Thickness), typeof(BorderView),
defaultValue: new Thickness(1));
/// <summary>
/// Gets or sets the width of the border.
/// </summary>
/// <value>The width of the border.</value>
public Thickness BorderWidth
{
get { return (Thickness)GetValue(BorderWidthProperty); }
set { SetValue(BorderWidthProperty, value); }
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (nameof(BorderColor).Equals(propertyName))
{
BackgroundColor = BorderColor;
}
if (nameof(BorderWidth).Equals(propertyName))
{
Padding = BorderWidth;
}
}
}
Et exemple d'utilisation (le résultat est identique à l'image ci-dessus):
<local:BorderView Grid.Row="0" Grid.Column="0" BorderColor="Gray" BorderWidth="0,2,0,2">
<Grid x:Name="prGrid1"
Padding="5,0,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
BackgroundColor="#EEEEEE">
<Label Text="only top and bottom set" Grid.Row="0" Grid.Column="0" />
</Grid>
</local:BorderView>
<local:BorderView Grid.Row="1" Grid.Column="0" BorderColor="Blue" BorderWidth="2">
<Grid x:Name="prGrid2"
Padding="5,0,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
BackgroundColor="Gray">
<Label Text="all border set" Grid.Row="0" Grid.Column="0" />
</Grid>
</local:BorderView>
<local:BorderView Grid.Row="2" Grid.Column="0" BorderColor="Red" BorderWidth="0,2,0,2">
<Grid x:Name="prGrid3"
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
BackgroundColor="Silver">
<Label Text="no horizontal borders" Grid.Row="0" Grid.Column="0" />
</Grid>
</local:BorderView>
</Grid>
</Grid>
Une autre option consiste à créer un PlatformEffect
personnalisé et quelques propriétés attachables liées afin d'implémenter une bordure pour tout contrôle visuel.
Propriétés et effet attachés (code portable/partagé):
public class VisualElementBorderEffect : RoutingEffect
{
public VisualElementBorderEffect() : base("MyCompany.VisualElementBorderEffect")
{
}
}
public static class BorderEffect
{
public static readonly BindableProperty HasBorderProperty =
BindableProperty.CreateAttached("HasBorder", typeof(bool), typeof(BorderEffect), false, propertyChanged: OnHasBorderChanged);
public static readonly BindableProperty ColorProperty =
BindableProperty.CreateAttached("Color", typeof(Color), typeof(BorderEffect), Color.Default);
public static readonly BindableProperty WidthProperty =
BindableProperty.CreateAttached("Width", typeof(Thickness), typeof(BorderEffect), new Thickness(0));
public static bool GetHasBorder(BindableObject view)
{
return (bool)view.GetValue(HasBorderProperty);
}
public static void SetHasBorder(BindableObject view, bool value)
{
view.SetValue(HasBorderProperty, value);
}
public static Color GetColor(BindableObject view)
{
return (Color)view.GetValue(ColorProperty);
}
public static void SetColor(BindableObject view, Color value)
{
view.SetValue(ColorProperty, value);
}
public static Thickness GetWidth(BindableObject view)
{
return (Thickness)view.GetValue(WidthProperty);
}
public static void SetWidth(BindableObject view, Thickness value)
{
view.SetValue(WidthProperty, value);
}
static void OnHasBorderChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as View;
if (view == null)
{
return;
}
bool hasBorder = (bool)newValue;
if (hasBorder)
{
view.Effects.Add(new VisualElementBorderEffect());
}
else
{
var toRemove = view.Effects.FirstOrDefault(e => e is VisualElementBorderEffect);
if (toRemove != null)
{
view.Effects.Remove(toRemove);
}
}
}
}
Effet de plateforme pour iOS:
[Assembly: ResolutionGroupName("MyCompany")]
[Assembly: ExportEffect(typeof(VisualElementBorderEffect), "VisualElementBorderEffect")]
namespace AppNamespace.iOS
{
public class BorderCALayer : CoreAnimation.CALayer { } //just create a type for easier replacement
public class VisualElementBorderEffect : PlatformEffect
{
protected override void OnAttached()
{
//no need to do anything here - we wait for size update to draw border
}
protected override void OnDetached()
{
RemoveBorderLayers();
}
void UpdateBorderLayers()
{
var box = Element as View;
if (box == null)
return;
RemoveBorderLayers(); //remove previous layers - this can further be optimized.
CGColor lineColor = BorderEffect.GetColor(Element).ToCGColor();
var borderWidth = BorderEffect.GetWidth(Element);
nfloat leftBorderWidth = new nfloat(borderWidth.Left);
nfloat rightBorderWidth = new nfloat(borderWidth.Right);
nfloat topBorderWidth = new nfloat(borderWidth.Top);
nfloat bottomBorderWidth = new nfloat(borderWidth.Bottom);
if (borderWidth.Left > 0)
{
var leftBorderLayer = new BorderCALayer();
leftBorderLayer.BackgroundColor = lineColor;
leftBorderLayer.Frame = new CGRect(0, 0, leftBorderWidth, box.Height);
InsertBorderLayer(leftBorderLayer);
}
if (borderWidth.Right > 0)
{
var rightBorderLayer = new BorderCALayer();
rightBorderLayer.BackgroundColor = lineColor;
rightBorderLayer.Frame = new CGRect(box.Width - borderWidth.Right, 0, rightBorderWidth, box.Height);
InsertBorderLayer(rightBorderLayer);
}
if (borderWidth.Top > 0)
{
var topBorderLayer = new BorderCALayer();
topBorderLayer.BackgroundColor = lineColor;
topBorderLayer.Frame = new CGRect(0, 0, box.Width, topBorderWidth);
InsertBorderLayer(topBorderLayer);
}
if (borderWidth.Bottom > 0)
{
var bottomBorderLayer = new BorderCALayer();
bottomBorderLayer.BackgroundColor = lineColor;
bottomBorderLayer.Frame = new CGRect(0, box.Height - borderWidth.Bottom, box.Width, bottomBorderWidth);
InsertBorderLayer(bottomBorderLayer);
}
}
void RemoveBorderLayers()
{
if ((Control ?? Container).Layer.Sublayers?.Length > 0)
{
var layers = (Control ?? Container).Layer.Sublayers.OfType<BorderCALayer>();
foreach (var layer in layers)
layer.RemoveFromSuperLayer();
}
}
void InsertBorderLayer(BorderCALayer layer)
{
var native = (Control ?? Container);
var index = (native.Layer.Sublayers?.Length > 0) ? native.Layer.Sublayers.Length - 1 : 0;
//This is needed to get every background redrawn if the color changes on runtime
native.Layer.InsertSublayer(layer, index);
}
protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(e);
//redraw border if any of these properties changed
if (e.PropertyName == VisualElement.WidthProperty.PropertyName ||
e.PropertyName == VisualElement.HeightProperty.PropertyName)
{
if(IsAttached && (Control != null || Container != null))
{
RemoveBorderLayers();
UpdateBorderLayers();
(Control ?? Container).SetNeedsDisplay();
}
}
}
}
}
Et exemple de code et sortie:
<StackLayout Margin="20">
<Grid x:Name="phraseGrid" BackgroundColor="Transparent"
Margin="0,55,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="10*" />
<RowDefinition Height="6*" />
<RowDefinition Height="80*" />
<RowDefinition Height="13*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid x:Name="prGrid1" Grid.Row="0" Grid.Column="0"
Padding="5,0,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
BackgroundColor="#EEEEEE"
local:BorderEffect.HasBorder="true"
local:BorderEffect.Color="Gray"
local:BorderEffect.Width="0,2,0,2">
<Label Text="grid with only top and bottom border set" Grid.Row="0" Grid.Column="0" />
</Grid>
<Grid x:Name="prGrid2" Grid.Row="1" Grid.Column="0"
Padding="5,0,0,0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
BackgroundColor="Gray"
local:BorderEffect.HasBorder="true"
local:BorderEffect.Color="Blue"
local:BorderEffect.Width="2">
<Label Text="grid with all border set" Grid.Row="0" Grid.Column="0" />
</Grid>
<Grid x:Name="prGrid3" Grid.Row="2" Grid.Column="0"
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
BackgroundColor="Silver"
local:BorderEffect.HasBorder="true"
local:BorderEffect.Color="Red"
local:BorderEffect.Width="0,2,0,2">
<Label Text="grid with no horizontal borders" Grid.Row="0" Grid.Column="0" />
<Label local:BorderEffect.HasBorder="true"
local:BorderEffect.Color="Maroon"
local:BorderEffect.Width="0,2,0,2"
Text="label with maroon border"
HorizontalOptions="Center"
VerticalOptions="Center" />
</Grid>
</Grid>
</StackLayout>