[英]command-driven visual state in Xamarin.Forms custom control
我想在 Xamarin.Forms 中创建一个命令驱动的自定义控件。 它应该像一个普通按钮一样,具有不同的正常状态和禁用状态。 一切运行良好,但我无法使用该命令的 CanExecute 属性获得绑定命令来驱动控件的可视 state。 我尝试使用 ButtonCommandPropertyChanged,但是当我在我的视图模型中调用 ChangeCanExecute() 时,它并没有被触发。
当然我可以在控件上引入另一个属性来更改 state,但我认为应该可以使用命令 CanExecute status 来做到这一点(并且应该更优雅......)
[图标按钮.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>
[图标按钮.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]
(我删除了一些不相关的部分)
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 的更改连接到我的自定义控件的可视化 state。
高度赞赏的任何帮助::-) 谢谢!
原因:
您将 Command MyDynamicCommand设置为只读,因此它永远不会被更改。
解决方案:
根据您的描述,我猜您想在点击其他帧时更改第三帧的边框颜色,对吧?
在运行时重新设置命令不是一个好的设计。 由于您已经定义了CommandParameter
。 您最好将ButtonState
的值绑定到它。
<viewModels:IconButton Grid.Row="2" Grid.Column="1"
ButtonText="Personal anfordern"
CommandParameter="{Binding ButtonState}"
ButtonCommand="{Binding MyDynamicCommand}" />
private void SetButtonState(bool newState)
{
ButtonState = newState;
}
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), typeof(IconButton), null, BindingMode.Default, null, ButtonCommandPropertyChanged);
在控件初始化时,将调用 ButtonCommandPropertyChanged 方法,该方法在 ButtonCommand 上为 CanExecute-changes 注册一个 EventHandler:
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 状态控制。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.