Xamarin.Forms
Création de contrôles personnalisés
Recherche…
Créez un contrôle d'entrée personnalisé Xamarin Forms (aucun élément natif requis)
Vous trouverez ci-dessous un exemple de contrôle personnalisé Xamarin Forms pur. Aucun rendu personnalisé n'est fait pour cela, mais pourrait facilement être implémenté, en fait, dans mon propre code, j'utilise ce même contrôle avec un moteur de rendu personnalisé pour le Label
et l' Entry
.
Le contrôle personnalisé est un ContentView
avec une Label
, une Entry
et un BoxView
, qui est maintenu en place à l'aide de 2 StackLayout
. Nous définissons également plusieurs propriétés pouvant être liées, ainsi qu'un événement TextChanged
.
Les propriétés personnalisables personnalisables sont définies comme elles sont ci-dessous et les éléments du contrôle (dans ce cas, une Label
et une Entry
) sont liés aux propriétés personnalisables. Quelques-unes des propriétés pouvant être liées doivent également implémenter un BindingPropertyChangedDelegate
afin que les éléments liés changent leurs valeurs.
public class InputFieldContentView : ContentView {
#region Properties
/// <summary>
/// Attached to the <c>InputFieldContentView</c>'s <c>ExtendedEntryOnTextChanged()</c> event, but returns the <c>sender</c> as <c>InputFieldContentView</c>.
/// </summary>
public event System.EventHandler<TextChangedEventArgs> OnContentViewTextChangedEvent; //In OnContentViewTextChangedEvent() we return our custom InputFieldContentView control as the sender but we could have returned the Entry itself as the sender if we wanted to do that instead.
public static readonly BindableProperty LabelTextProperty = BindableProperty.Create("LabelText", typeof(string), typeof(InputFieldContentView), string.Empty);
public string LabelText {
get { return (string)GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public static readonly BindableProperty LabelColorProperty = BindableProperty.Create("LabelColor", typeof(Color), typeof(InputFieldContentView), Color.Default);
public Color LabelColor {
get { return (Color)GetValue(LabelColorProperty); }
set { SetValue(LabelColorProperty, value); }
}
public static readonly BindableProperty EntryTextProperty = BindableProperty.Create("EntryText", typeof(string), typeof(InputFieldContentView), string.Empty, BindingMode.TwoWay, null, OnEntryTextChanged);
public string EntryText {
get { return (string)GetValue(EntryTextProperty); }
set { SetValue(EntryTextProperty, value); }
}
public static readonly BindableProperty PlaceholderTextProperty = BindableProperty.Create("PlaceholderText", typeof(string), typeof(InputFieldContentView), string.Empty);
public string PlaceholderText {
get { return (string)GetValue(PlaceholderTextProperty); }
set { SetValue(PlaceholderTextProperty, value); }
}
public static readonly BindableProperty UnderlineColorProperty = BindableProperty.Create("UnderlineColor", typeof(Color), typeof(InputFieldContentView), Color.Black, BindingMode.TwoWay, null, UnderlineColorChanged);
public Color UnderlineColor {
get { return (Color)GetValue(UnderlineColorProperty); }
set { SetValue(UnderlineColorProperty, value); }
}
private BoxView _underline;
#endregion
public InputFieldContentView() {
BackgroundColor = Color.Transparent;
HorizontalOptions = LayoutOptions.FillAndExpand;
Label label = new Label {
BindingContext = this,
HorizontalOptions = LayoutOptions.StartAndExpand,
VerticalOptions = LayoutOptions.Center,
TextColor = Color.Black
};
label.SetBinding(Label.TextProperty, (InputFieldContentView view) => view.LabelText, BindingMode.TwoWay);
label.SetBinding(Label.TextColorProperty, (InputFieldContentView view) => view.LabelColor, BindingMode.TwoWay);
Entry entry = new Entry {
BindingContext = this,
HorizontalOptions = LayoutOptions.End,
TextColor = Color.Black,
HorizontalTextAlignment = TextAlignment.End
};
entry.SetBinding(Entry.PlaceholderProperty, (InputFieldContentView view) => view.PlaceholderText, BindingMode.TwoWay);
entry.SetBinding(Entry.TextProperty, (InputFieldContentView view) => view.EntryText, BindingMode.TwoWay);
entry.TextChanged += OnTextChangedEvent;
_underline = new BoxView {
BackgroundColor = Color.Black,
HeightRequest = 1,
HorizontalOptions = LayoutOptions.FillAndExpand
};
Content = new StackLayout {
Spacing = 0,
HorizontalOptions = LayoutOptions.FillAndExpand,
Children = {
new StackLayout {
Padding = new Thickness(5, 0),
Spacing = 0,
HorizontalOptions = LayoutOptions.FillAndExpand,
Orientation = StackOrientation.Horizontal,
Children = { label, entry }
}, _underline
}
};
SizeChanged += (sender, args) => entry.WidthRequest = Width * 0.5 - 10;
}
private static void OnEntryTextChanged(BindableObject bindable, object oldValue, object newValue) {
InputFieldContentView contentView = (InputFieldContentView)bindable;
contentView.EntryText = (string)newValue;
}
private void OnTextChangedEvent(object sender, TextChangedEventArgs args) {
if(OnContentViewTextChangedEvent != null) { OnContentViewTextChangedEvent(this, new TextChangedEventArgs(args.OldTextValue, args.NewTextValue)); } //Here is where we pass in 'this' (which is the InputFieldContentView) instead of 'sender' (which is the Entry control)
}
private static void UnderlineColorChanged(BindableObject bindable, object oldValue, object newValue) {
InputFieldContentView contentView = (InputFieldContentView)bindable;
contentView._underline.BackgroundColor = (Color)newValue;
}
}
Et voici une image du produit final sur iOS (l'image montre à quoi elle ressemble lors de l'utilisation d'un moteur de rendu personnalisé pour l' Label
et l' Entry
utilisées pour supprimer la bordure sur iOS et pour spécifier une police personnalisée pour les deux éléments):
Un problème que j'ai rencontré BoxView.BackgroundColor
à faire changer BoxView.BackgroundColor
lorsque UnderlineColor
changeait. Même après la liaison du BoxView
de BackgroundColor
propriété, cela ne changerait pas jusqu'à ce que j'ajouté le UnderlineColorChanged
délégué.
Étiquette avec une collection pouvant être liée de Spans
J'ai créé une étiquette personnalisée avec un wrapper autour de la propriété FormattedText
:
public class MultiComponentLabel : Label
{
public IList<TextComponent> Components { get; set; }
public MultiComponentLabel()
{
var components = new ObservableCollection<TextComponent>();
components.CollectionChanged += OnComponentsChanged;
Components = components;
}
private void OnComponentsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
BuildText();
}
private void OnComponentPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
BuildText();
}
private void BuildText()
{
var formattedString = new FormattedString();
foreach (var component in Components)
{
formattedString.Spans.Add(new Span { Text = component.Text });
component.PropertyChanged -= OnComponentPropertyChanged;
component.PropertyChanged += OnComponentPropertyChanged;
}
FormattedText = formattedString;
}
}
J'ai ajouté une collection de TextComponent
personnalisés:
public class TextComponent : BindableObject
{
public static readonly BindableProperty TextProperty =
BindableProperty.Create(nameof(Text),
typeof(string),
typeof(TextComponent),
default(string));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
Et lorsque la collection de composants de texte change ou que la propriété Text
d'un composant distinct change, je reconstruit la propriété FormattedText
de l' Label
de base.
Et comment je l'ai utilisé dans XAML
:
<ContentPage x:Name="Page"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:SuperForms.Controls;assembly=SuperForms.Controls"
x:Class="SuperForms.Samples.MultiComponentLabelPage">
<controls:MultiComponentLabel Margin="0,20,0,0">
<controls:MultiComponentLabel.Components>
<controls:TextComponent Text="Time"/>
<controls:TextComponent Text=": "/>
<controls:TextComponent Text="{Binding CurrentTime, Source={x:Reference Page}}"/>
</controls:MultiComponentLabel.Components>
</controls:MultiComponentLabel>
</ContentPage>
Codebehind de la page:
public partial class MultiComponentLabelPage : ContentPage
{
private string _currentTime;
public string CurrentTime
{
get { return _currentTime; }
set
{
_currentTime = value;
OnPropertyChanged();
}
}
public MultiComponentLabelPage()
{
InitializeComponent();
BindingContext = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
CurrentTime = DateTime.Now.ToString("hh : mm : ss");
return true;
});
}
}
Création d'un contrôle d'entrée personnalisé avec une propriété MaxLength
Le contrôle Xamarin Forms Entry
ne possède pas de propriété MaxLength
. Pour ce faire, vous pouvez étendre Entry
comme ci-dessous, en ajoutant une propriété Bindable MaxLength
. Ensuite, il vous suffit de vous inscrire à l’événement TextChanged
sur Entry
et de valider la longueur du Text
à l’appel suivant:
class CustomEntry : Entry
{
public CustomEntry()
{
base.TextChanged += Validate;
}
public static readonly BindableProperty MaxLengthProperty = BindableProperty.Create(nameof(MaxLength), typeof(int), typeof(CustomEntry), 0);
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
public void Validate(object sender, TextChangedEventArgs args)
{
var e = sender as Entry;
var val = e?.Text;
if (string.IsNullOrEmpty(val))
return;
if (MaxLength > 0 && val.Length > MaxLength)
val = val.Remove(val.Length - 1);
e.Text = val;
}
}
Utilisation dans XAML:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:customControls="clr-namespace:CustomControls;assembly=CustomControls"
x:Class="Views.TestView">
<ContentView.Content>
<customControls:CustomEntry MaxLength="10" />
</ContentView.Content>