Xamarin.Forms Content of a button

后端 未结 6 1045
我寻月下人不归
我寻月下人不归 2021-02-15 16:13

I\'m trying to add a custom content to a button in Xamarin Forms.

By default Button is created like this:

相关标签:
6条回答
  • 2021-02-15 16:41

    Here is my implementation of a button with content, that has some additional things to better mimic an actual button:

    • ICommand Command
    • object CommandParameter
    • Events:
      • Clicked
      • Pressed
      • Released
      • VisuallyPressedChanged (Occurs when the style of the button should be changed to let the user know that the button has been pressed)

    It uses TouchTracking.Forms package for handling touch events, you can download it using 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;
                }
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-15 16:42

    I stumbled upon a problem, which turned out to be a recent bug described here.

    It forced me to the following hack, which I think is useful to know for others. When setting BackgroundColor directly in my ShoppingCartSummaryView, TapGestureRecognizer did not work.

        <!--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>
    
    0 讨论(0)
  • 2021-02-15 16:46

    Thanks Paul,

    I have created my own UserControl which handles that

    Here it is:

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

    And view code:

    <?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>
    
    0 讨论(0)
  • 2021-02-15 16:54

    Thanks Tomasz, good inspiration. However for me, your control didn´t pick up the tap-event on all platforms and used obsolete Xamarin.Forms methods "BindableProperty.Create<" and . So I came up with this one.

    Here it is:

    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);
        }
    }
    
    0 讨论(0)
  • 2021-02-15 16:54

    Thanks Tomasz for creating ContentButton.

    I have used it successfully, adding a CommandParameter in a similar way:

    <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); }
    }
    
    0 讨论(0)
  • 2021-02-15 16:56

    Unfortunately, at the moment Xamarin.Forms does not support a Content property for the Button.

    You would have to create your own User Control with a combination of other controls to try and recreate a Button control. You could then make part of your User Control a ContentView, create a BindableProperty to bind your Grid to, then use the a BindingPropertyChangedDelegate to assign the Content property of your ContentView.

    0 讨论(0)
提交回复
热议问题