Xamarin.Forms自定义控件中的命令驱动视觉状态 xaml 在视图模型中在IconButton.xaml.cs

我想在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,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),default(ImageSource));

    public ImageSource ButtonIcon
    {
        get => (ImageSource)Getvalue(ButtonIconProperty);
        set => Setvalue(ButtonIconProperty,value);
    }

    public static readonly BindableProperty ButtonCommandProperty =
        BindableProperty.Create("ButtonCommand",typeof(ICommand),null,BindingMode.Default,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),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]

(我删除了一些不相关的部分)

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的更改连接到我的自定义控件的可视状态。

任何受到高度赞赏的帮助! :-)谢谢!

kyeky 回答:Xamarin.Forms自定义控件中的命令驱动视觉状态 xaml 在视图模型中在IconButton.xaml.cs

原因:

您将命令 MyDynamicCommand 设置为只读,因此它将永远不会被更改。

解决方案:

根据您的描述,我想您想在点击其他帧时更改第三帧的边框颜色,对吗?

在运行时重新设置命令不是一个好的设计。由于您已经定义了CommandParameter。您最好将ButtonState的值绑定到它。

xaml

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

在视图模型中

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 ;


            });
,

好的,我想我终于明白了...这种机制的工作方式如下:

自定义控件对命令的BindableProperty使用扩展的构造函数(请注意BindingPropertyChangedDelegate):

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

在控件初始化时,调用ButtonCommandPropertyChanged方法,该方法在ButtonCommand上注册一个EventHandler以进行CanExecute更改:

private static void ButtonCommandPropertyChanged(BindableObject bindable,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状态控制。

本文链接:https://www.f2er.com/2508260.html

大家都在问