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;
            }
        }
    }
}