Xamarin.Forms 一个按钮的内容
Xamarin.Forms Content of a button
我正在尝试向 Xamarin Forms 中的按钮添加自定义内容。
默认情况下 Button 是这样创建的:
<Button d:DataContext="{d:DesignInstance viewModel:AssessmentItemCategory}"
Clicked="Button_OnClicked"
Style="{StaticResource CategoryButtonStyle}"
Text={Binding Text} />
但我想创建此按钮的自定义内容。通常使用 WPF 我会这样做:
<Button d:DataContext="{d:DesignInstance viewModel:AssessmentItemCategory}"
Clicked="Button_OnClicked"
Style="{StaticResource CategoryButtonStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Text}" Grid.Column="0" TextColor="Black"/>
<Label Text="-" Grid.Column="1" TextColor="Black"/>
</Grid>
</Button>
但这不起作用。
我也在找 DataTemplate 属性,但没找到。
如何在Xamarin.Forms中完成?
遗憾的是,目前 Xamarin.Forms 不支持按钮的内容 属性。
您必须结合其他控件来创建自己的用户控件,才能尝试重新创建 Button 控件。然后,您可以将用户控件的一部分设为 ContentView,创建 BindableProperty 以将您的网格绑定到,然后使用 BindingPropertyChangedDelegate 分配 ContentView 的内容 属性。
谢谢保罗,
我已经创建了自己的 UserControl 来处理
这里是:
public partial class ContentButton : ContentView
{
public ContentButton()
{
InitializeComponent();
}
public event EventHandler Tapped;
public static readonly BindableProperty CommandProperty = BindableProperty.Create<ContentButton, ICommand>(c => c.Command, null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
private void TapGestureRecognizer_OnTapped(object sender, EventArgs e)
{
if(Tapped != null)
Tapped(this,new EventArgs());
}
}
并查看代码:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="RFA.Wireframes.Controls.ContentButton"
x:Name="ContentButtonView">
<ContentView.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_OnTapped" Command="{Binding Source={x:Reference ContentButtonView}, Path=Command}"></TapGestureRecognizer>
</ContentView.GestureRecognizers>
</ContentView>
感谢 Tomasz 创建 ContentButton
。
我已经用成功了,加一个CommandParameter
类似的方法:
<ContentView.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_OnTapped" Command="{Binding Source={x:Reference ContentButtonView}, Path=Command}" CommandParameter="{Binding Source={x:Reference ContentButtonView}, Path=CommandParameter}"/>
</ContentView.GestureRecognizers>
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ContentButton));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
我偶然发现了一个问题,原来是最近描述的一个错误 here。
它迫使我进行以下黑客攻击,我认为这对其他人来说很有用。当直接在我的ShoppingCartSummaryView
中设置BackgroundColor
时,TapGestureRecognizer
没有起作用。
<!--HACK Setting BackgroundColor here because of https://bugzilla.xamarin.com/show_bug.cgi?id=25943 -->
<Controls:ContentButton Grid.Row="2" Command="{Binding ShowCartCommand}" BackgroundColor="Yellow">
<Controls:ContentButton.Content>
<Views:ShoppingCartSummaryView/>
</Controls:ContentButton.Content>
</Controls:ContentButton>
感谢 Tomasz,很好的灵感。但是对我来说,您的控件并没有在所有平台上接收点击事件,而是使用了过时的 Xamarin.Forms 方法 "BindableProperty.Create<" 和 .所以我想出了这个。
这里是:
public class ContentButton:ContentView
{
private readonly TapGestureRecognizer _tapGestureRecognizer;
public ContentButton()
{
_tapGestureRecognizer = new TapGestureRecognizer();
GestureRecognizers.Add(_tapGestureRecognizer);
}
protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);
if (child is View childview)
{
childview.GestureRecognizers.Add(_tapGestureRecognizer);
}
}
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand),
typeof(ContentButton), null, BindingMode.Default, null, CommandPropertyChanged);
private static void CommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (newValue is ICommand command && bindable is ContentButton contentButton)
{
contentButton._tapGestureRecognizer.Command = command;
}
}
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
}
这是我实现的带有内容的按钮,它有一些额外的东西可以更好地模仿实际按钮:
ICommand Command
object CommandParameter
- 事件:
Clicked
Pressed
Released
VisuallyPressedChanged
(当按钮的样式应该改变以让用户知道按钮已被按下时发生)
它使用 TouchTracking.Forms 包来处理触摸事件,您可以使用 NuGet 下载它。
public class ContentButton : ContentView
{
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(ContentButton), null, BindingMode.Default);
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ContentButton));
/// <summary>
/// Occurs when the Button is clicked.
/// </summary>
public event EventHandler Clicked;
/// <summary>
/// Occurs when the Button is pressed.
/// </summary>
public event EventHandler Pressed;
/// <summary>
/// Occurs when the Button is released.
/// <para>The released event always occur before the clicked event.</para>
/// </summary>
public event EventHandler Released;
/// <summary>
/// Occurs when the style of the button should be changed to let the user know that the button has been pressed.
/// <para>If the argument is true, it means that the Button was just pressed.
/// <para>If the argument is false, it means that the Button was just released or that the user has moved his finger out of the buttons boundaries.</para>
/// </summary>
public event EventHandler<bool> VisuallyPressedChanged;
/// <summary>
/// Gets or sets the command to invoke when the button is activated. This is a bindable property.
/// </summary>
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
/// <summary>
/// Gets or sets the parameter to pass to the Command property. This is a bindable property.
/// </summary>
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
private bool isVisuallyPressed;
public ContentButton()
{
var touchEffect = new TouchEffect
{
Capture = true
};
touchEffect.TouchAction += TouchEffect_TouchAction;
Effects.Add(touchEffect);
}
protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);
// so that the touch events are ignored and bypassed to this control
if(child is VisualElement visualChild) {
visualChild.InputTransparent = true;
}
}
private long? currentId;
private object touchEffect_lock = new object();
private void TouchEffect_TouchAction(object sender, TouchActionEventArgs e)
{
// only track one touch
if(currentId != e.Id && e.Type!=TouchActionType.Pressed) {
return;
}
lock(touchEffect_lock) {
switch(e.Type) {
case TouchActionType.Pressed:
currentId = e.Id;
Pressed?.Invoke(this, EventArgs.Empty);
isVisuallyPressed = true;
VisuallyPressedChanged?.Invoke(this, true);
break;
case TouchActionType.Moved:
if(isVisuallyPressed) {
bool isInside = e.Location.X >= 0 && e.Location.Y >= 0 && e.Location.X <= Bounds.Width && e.Location.Y <= Bounds.Height;
if(!isInside) {
isVisuallyPressed = false;
VisuallyPressedChanged?.Invoke(this, false);
}
}
break;
case TouchActionType.Cancelled:
currentId = null;
isVisuallyPressed = false;
VisuallyPressedChanged?.Invoke(this, false);
break;
case TouchActionType.Released:
currentId = null;
if(isVisuallyPressed) {
Released?.Invoke(this, EventArgs.Empty);
Clicked?.Invoke(this, EventArgs.Empty);
if(Command != null && Command.CanExecute(CommandParameter)) {
Command.Execute(CommandParameter);
}
isVisuallyPressed = false;
VisuallyPressedChanged?.Invoke(this, false);
}
break;
}
}
}
}
我正在尝试向 Xamarin Forms 中的按钮添加自定义内容。
默认情况下 Button 是这样创建的:
<Button d:DataContext="{d:DesignInstance viewModel:AssessmentItemCategory}"
Clicked="Button_OnClicked"
Style="{StaticResource CategoryButtonStyle}"
Text={Binding Text} />
但我想创建此按钮的自定义内容。通常使用 WPF 我会这样做:
<Button d:DataContext="{d:DesignInstance viewModel:AssessmentItemCategory}"
Clicked="Button_OnClicked"
Style="{StaticResource CategoryButtonStyle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Text}" Grid.Column="0" TextColor="Black"/>
<Label Text="-" Grid.Column="1" TextColor="Black"/>
</Grid>
</Button>
但这不起作用。
我也在找 DataTemplate 属性,但没找到。
如何在Xamarin.Forms中完成?
遗憾的是,目前 Xamarin.Forms 不支持按钮的内容 属性。
您必须结合其他控件来创建自己的用户控件,才能尝试重新创建 Button 控件。然后,您可以将用户控件的一部分设为 ContentView,创建 BindableProperty 以将您的网格绑定到,然后使用 BindingPropertyChangedDelegate 分配 ContentView 的内容 属性。
谢谢保罗,
我已经创建了自己的 UserControl 来处理
这里是:
public partial class ContentButton : ContentView
{
public ContentButton()
{
InitializeComponent();
}
public event EventHandler Tapped;
public static readonly BindableProperty CommandProperty = BindableProperty.Create<ContentButton, ICommand>(c => c.Command, null);
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
private void TapGestureRecognizer_OnTapped(object sender, EventArgs e)
{
if(Tapped != null)
Tapped(this,new EventArgs());
}
}
并查看代码:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="RFA.Wireframes.Controls.ContentButton"
x:Name="ContentButtonView">
<ContentView.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_OnTapped" Command="{Binding Source={x:Reference ContentButtonView}, Path=Command}"></TapGestureRecognizer>
</ContentView.GestureRecognizers>
</ContentView>
感谢 Tomasz 创建 ContentButton
。
我已经用成功了,加一个CommandParameter
类似的方法:
<ContentView.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_OnTapped" Command="{Binding Source={x:Reference ContentButtonView}, Path=Command}" CommandParameter="{Binding Source={x:Reference ContentButtonView}, Path=CommandParameter}"/>
</ContentView.GestureRecognizers>
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ContentButton));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
我偶然发现了一个问题,原来是最近描述的一个错误 here。
它迫使我进行以下黑客攻击,我认为这对其他人来说很有用。当直接在我的ShoppingCartSummaryView
中设置BackgroundColor
时,TapGestureRecognizer
没有起作用。
<!--HACK Setting BackgroundColor here because of https://bugzilla.xamarin.com/show_bug.cgi?id=25943 -->
<Controls:ContentButton Grid.Row="2" Command="{Binding ShowCartCommand}" BackgroundColor="Yellow">
<Controls:ContentButton.Content>
<Views:ShoppingCartSummaryView/>
</Controls:ContentButton.Content>
</Controls:ContentButton>
感谢 Tomasz,很好的灵感。但是对我来说,您的控件并没有在所有平台上接收点击事件,而是使用了过时的 Xamarin.Forms 方法 "BindableProperty.Create<" 和 .所以我想出了这个。
这里是:
public class ContentButton:ContentView
{
private readonly TapGestureRecognizer _tapGestureRecognizer;
public ContentButton()
{
_tapGestureRecognizer = new TapGestureRecognizer();
GestureRecognizers.Add(_tapGestureRecognizer);
}
protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);
if (child is View childview)
{
childview.GestureRecognizers.Add(_tapGestureRecognizer);
}
}
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand),
typeof(ContentButton), null, BindingMode.Default, null, CommandPropertyChanged);
private static void CommandPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
if (newValue is ICommand command && bindable is ContentButton contentButton)
{
contentButton._tapGestureRecognizer.Command = command;
}
}
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
}
这是我实现的带有内容的按钮,它有一些额外的东西可以更好地模仿实际按钮:
ICommand Command
object CommandParameter
- 事件:
Clicked
Pressed
Released
VisuallyPressedChanged
(当按钮的样式应该改变以让用户知道按钮已被按下时发生)
它使用 TouchTracking.Forms 包来处理触摸事件,您可以使用 NuGet 下载它。
public class ContentButton : ContentView
{
public static readonly BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(ContentButton), null, BindingMode.Default);
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create(nameof(CommandParameter), typeof(object), typeof(ContentButton));
/// <summary>
/// Occurs when the Button is clicked.
/// </summary>
public event EventHandler Clicked;
/// <summary>
/// Occurs when the Button is pressed.
/// </summary>
public event EventHandler Pressed;
/// <summary>
/// Occurs when the Button is released.
/// <para>The released event always occur before the clicked event.</para>
/// </summary>
public event EventHandler Released;
/// <summary>
/// Occurs when the style of the button should be changed to let the user know that the button has been pressed.
/// <para>If the argument is true, it means that the Button was just pressed.
/// <para>If the argument is false, it means that the Button was just released or that the user has moved his finger out of the buttons boundaries.</para>
/// </summary>
public event EventHandler<bool> VisuallyPressedChanged;
/// <summary>
/// Gets or sets the command to invoke when the button is activated. This is a bindable property.
/// </summary>
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
/// <summary>
/// Gets or sets the parameter to pass to the Command property. This is a bindable property.
/// </summary>
public object CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
private bool isVisuallyPressed;
public ContentButton()
{
var touchEffect = new TouchEffect
{
Capture = true
};
touchEffect.TouchAction += TouchEffect_TouchAction;
Effects.Add(touchEffect);
}
protected override void OnChildAdded(Element child)
{
base.OnChildAdded(child);
// so that the touch events are ignored and bypassed to this control
if(child is VisualElement visualChild) {
visualChild.InputTransparent = true;
}
}
private long? currentId;
private object touchEffect_lock = new object();
private void TouchEffect_TouchAction(object sender, TouchActionEventArgs e)
{
// only track one touch
if(currentId != e.Id && e.Type!=TouchActionType.Pressed) {
return;
}
lock(touchEffect_lock) {
switch(e.Type) {
case TouchActionType.Pressed:
currentId = e.Id;
Pressed?.Invoke(this, EventArgs.Empty);
isVisuallyPressed = true;
VisuallyPressedChanged?.Invoke(this, true);
break;
case TouchActionType.Moved:
if(isVisuallyPressed) {
bool isInside = e.Location.X >= 0 && e.Location.Y >= 0 && e.Location.X <= Bounds.Width && e.Location.Y <= Bounds.Height;
if(!isInside) {
isVisuallyPressed = false;
VisuallyPressedChanged?.Invoke(this, false);
}
}
break;
case TouchActionType.Cancelled:
currentId = null;
isVisuallyPressed = false;
VisuallyPressedChanged?.Invoke(this, false);
break;
case TouchActionType.Released:
currentId = null;
if(isVisuallyPressed) {
Released?.Invoke(this, EventArgs.Empty);
Clicked?.Invoke(this, EventArgs.Empty);
if(Command != null && Command.CanExecute(CommandParameter)) {
Command.Execute(CommandParameter);
}
isVisuallyPressed = false;
VisuallyPressedChanged?.Invoke(this, false);
}
break;
}
}
}
}