управляемое командой визуальное состояние в пользовательском элементе управления Xamarin.Forms - PullRequest
0 голосов
/ 05 апреля 2020

Я хотел бы создать управляемый командой пользовательский элемент управления в Xamarin.Forms. Он должен действовать как обычная кнопка с разными состояниями для обычного отключенного. Все работает очень хорошо, но я не могу заставить связанную команду управлять визуальным состоянием элемента управления с помощью свойства CanExecute этой команды. Я пытался использовать ButtonCommandPropertyChanged, но он не срабатывает, когда я вызываю ChangeCanExecute () в моей модели представления.

Конечно, я мог бы ввести другое свойство в элементе управления, чтобы изменить состояние, но я думаю, что это должно быть возможно сделать это с помощью команд CanExecute status (и должно быть более элегантно ...)

[IconButton.xaml]

<?xml version="1.0" encoding="UTF-8"?>
<ContentView WidthRequest="180" HeightRequest="90"
         xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:d="http://xamarin.com/schemas/2014/forms/design"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         mc:Ignorable="d"
         x:Class="TestStyle.Controls.IconButton">

<ContentView.Content>
    <Frame x:Name="MyFrame" Style="{StaticResource StandardFrameStyle}" Margin="5" Padding="10,12,10,10">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="Normal">
                    <VisualState.Setters>
                        <Setter Property="BorderColor" Value="Black"></Setter>
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Disabled">
                    <VisualState.Setters>
                        <Setter Property="BorderColor" Value="Red"></Setter>
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Grid RowSpacing="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="4*" />
                <RowDefinition Height="3*" />
            </Grid.RowDefinitions>

            <Image x:Name="InnerImage"
                   Grid.Row="0"
                   HorizontalOptions="Center"
                   Aspect="AspectFit"
                   Margin="0,0,0,1"
                   HeightRequest="35"/>
            <Label x:Name="InnerLabel"
                   Grid.Row="1"
                   VerticalOptions="CenterAndExpand"
                   HorizontalTextAlignment="Center"
                   TextColor="{StaticResource TextColor}"
                   FontSize="12"
                   LineHeight="0.9"
                   MaxLines="2"/>
        </Grid>
    </Frame>
</ContentView.Content>

[IconButton.xaml.cs ]

using System;
using System.Diagnostics;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace TestStyle.Controls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class IconButton : ContentView
{
    public EventHandler Clicked;

    public static readonly BindableProperty ButtonTextProperty =
        BindableProperty.Create("ButtonText", typeof(string), typeof(IconButton), default(string));

    public string ButtonText
    {
        get => ((string)GetValue(ButtonTextProperty))?.ToUpper();
        set => SetValue(ButtonTextProperty, value);
    }

    public static readonly BindableProperty ButtonIconProperty =
        BindableProperty.Create("ButtonIcon", typeof(ImageSource), typeof(IconButton), default(ImageSource));

    public ImageSource ButtonIcon
    {
        get => (ImageSource)GetValue(ButtonIconProperty);
        set => SetValue(ButtonIconProperty, value);
    }

    public static readonly BindableProperty ButtonCommandProperty =
        BindableProperty.Create("ButtonCommand", typeof(ICommand), typeof(IconButton), null, BindingMode.Default, null, ButtonCommandPropertyChanged, ButtonCommandPropertyChanged);

    private static void ButtonCommandPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
    {
        Debug.WriteLine($"oldValue: ${oldvalue}");
        Debug.WriteLine($"newValue: ${newvalue}");
    }

    public ICommand ButtonCommand
    {
        get => (ICommand)GetValue(ButtonCommandProperty);
        set
        {
            SetValue(ButtonCommandProperty, value);
            Debug.WriteLine("ButtonCommand wurde gesetzt!!");
        }
    }

    public static readonly BindableProperty CommandParameterProperty = 
        BindableProperty.Create("CommandParameter", typeof(object), typeof(IconButton), null);

    public object CommandParameter
    {
        get => (object)GetValue(CommandParameterProperty);
        set => SetValue(CommandParameterProperty, value);
    }

    public IconButton()
    {
        InitializeComponent();
        this.GestureRecognizers.Add(new TapGestureRecognizer
        {
            Command = new Command(() =>
            {
                Clicked?.Invoke(this, EventArgs.Empty);
                if (ButtonCommand == null) return;
                if (ButtonCommand.CanExecute(CommandParameter))
                    ButtonCommand.Execute(CommandParameter);
            })
        });
    }
}
}

[FirstPage.xaml]

(я удаляю некоторые несущественные части)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:controls="clr-namespace:TestStyle.Controls;assembly=TestStyle"
             xmlns:viewModels="clr-namespace:TestStyle.ViewModels;assembly=TestStyle"
             x:DataType="viewModels:ExampleViewModel"
             mc:Ignorable="d"
             x:Class="TestStyle.Views.FirstPage"
             BackgroundColor="{StaticResource LightBgColor}"
             Title="Test"
             NavigationPage.HasNavigationBar="false">

    <ContentPage.BindingContext>
        <viewModels:ExampleViewModel/>
    </ContentPage.BindingContext>

    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition />
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <controls:IconButton Grid.Row="1" Grid.Column="0"
                             ButtonText="Enable"
                             ButtonIcon="shuffle_gr.png"
                             ButtonCommand="{Binding EnableCommand}" />

        <controls:IconButton Grid.Row="1" Grid.Column="1"
                             ButtonText="Disable"
                             ButtonIcon="clock_gr.png"
                             ButtonCommand="{Binding DisableCommand}" />

        <controls:IconButton Grid.Row="2" Grid.Column="1"
                             ButtonText="Personal anfordern"
                             ButtonIcon="select_gr.png"
                             ButtonCommand="{Binding MyDynamicCommand}" />


        <Label Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding StatusMessage}" HorizontalTextAlignment="Center"/>

    </Grid>

</ContentPage>

[ExampleViewModel.cs]

(I удалите некоторые ненужные части)

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using TestStyle.Models;
using TestStyle.Views;
using Xamarin.Forms;

namespace TestStyle.ViewModels
{
    public class ExampleViewModel : INotifyPropertyChanged
    {
        private string _statusMessage;
        private bool _buttonState;
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public string StatusMessage
        {
            get => _statusMessage;
            set
            {
                if (_statusMessage == value) return;
                _statusMessage = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("StatusMessage"));
            }
        }

        public bool ButtonState
        {
            get
            {
                return _buttonState;
            }
            set
            {
                _buttonState = value;
                OnPropertyChanged(nameof(ButtonState));
            }
        }

        public ICommand MyDynamicCommand { get; }
        public ICommand EnableCommand { get; }
        public ICommand DisableCommand { get; }

        public ExampleViewModel()
        {
            SetButtonState(true);
            MyDynamicCommand = new Command(() => StatusMessage = $"This is dynamic at {DateTime.Now.ToLongTimeString()}", () => ButtonState);
            EnableCommand = new Command(() => SetButtonState(true));
            DisableCommand = new Command(() => SetButtonState(false));
        }


        private void SetButtonState(bool newState)
        {
            ButtonState = newState;
            var myCmd = ((Command) MyDynamicCommand);
            if (myCmd != null)
            {
                myCmd.ChangeCanExecute(); // I think my control should be notified here!?
            }                
        }
    }
}

Я не знаю, как связать изменение CanExecute с визуальным состоянием моего пользовательского элемента управления.

Любая помощь высоко ценится! :-) Спасибо!

Ответы [ 2 ]

0 голосов
/ 06 апреля 2020

ОК, думаю, я наконец-то понял ... Этот механизм работает следующим образом:

В пользовательском элементе управления используется расширенный конструктор для BindableProperty команды (обратите внимание на BindingPropertyChangedDelegate):

public static readonly BindableProperty ButtonCommandProperty =
            BindableProperty.Create("ButtonCommand", typeof(ICommand), typeof(IconButton), null, BindingMode.Default, null, ButtonCommandPropertyChanged);

При инициализации элемента управления вызывается метод ButtonCommandPropertyChanged, который регистрирует EventHandler в ButtonCommand для CanExecute-изменений:

private static void ButtonCommandPropertyChanged(BindableObject bindable, object oldvalue, object newvalue)
{
    // wird bei Initialisierung des Buttons aufgerufen
    if (!(newvalue is Command)) return;
    var buttonCommand = (ICommand) newvalue;
    var iconButton = (IconButton) bindable;
    iconButton.ChangeEnabledState(buttonCommand.CanExecute(iconButton.ButtonCommandParameter));
    buttonCommand.CanExecuteChanged += (sender, args) =>
        IconButton_CanExecuteChanged(sender, new BindableObjectEventArgs(bindable));
}

private static void IconButton_CanExecuteChanged(object sender, BindableObjectEventArgs e)
{
    // CanExecuteChanged wurde ausgelöst, CanExecute muss erneut ausgewertet werden
    var iconButton = (IconButton) e.BindableObject;
    var executable = ((ICommand) sender).CanExecute(iconButton.ButtonCommandParameter);
    iconButton.ChangeEnabledState(executable);
}

private void ChangeEnabledState(bool state) => VisualStateManager.GoToState(MyFrame, state ? "Normal" : "Disabled");

В моем ViewModel мне нужно сообщить моей команде, когда CanExecute должен быть переоценены:

private void SetButtonState(bool newState)

{
    ButtonState = newState;
    var myCmd = ((Command) MyDynamicCommand);
    myCmd?.ChangeCanExecute(); // Command von der Änderung seines CanExecute-Status benachrichtigen (löst CanExecuteChanged-Event aus)
}

Вот и все. VisualState пользовательского элемента управления теперь управляется статусом команды CanExecute.

0 голосов
/ 06 апреля 2020

Причина:

Команда MyDynamicCommand установлена ​​только для чтения, поэтому она никогда не будет изменена.

Решение:

Из вашего описания, я думаю, что вы хотите изменить цвет границы третьего кадра при нажатии на другие кадры, верно?

Это не очень хороший дизайн для сброса команда во время выполнения. Так как вы определили CommandParameter. Вам лучше привязать к нему значение ButtonState.

в xaml

<viewModels:IconButton Grid.Row="2" Grid.Column="1"
                             ButtonText="Personal anfordern"
                             CommandParameter="{Binding ButtonState}"
                             ButtonCommand="{Binding MyDynamicCommand}" />

в viewmodel

private void SetButtonState(bool newState)
{
  ButtonState = newState;         
}

в IconButton.xaml.cs

public static readonly BindableProperty CommandParameterProperty =
            BindableProperty.Create("CommandParameter", typeof(object), typeof(IconButton), null,propertyChanged:(BindableObject bindable, object oldvalue, object newvalue) => {


                var iconbutton = bindable as IconButton;

                iconbutton.MyFrame.IsEnabled = (bool)newvalue ;


            });
...