我查看了您的示例代码,您似乎正在使用Xamarin 表单示例代码来实现您的EventToCommandBehavior
. 这也在Xamarin 社区工具包中以大致相同的方式实现。请注意,这些实现继承自Xamarin.Forms.Behavior
.
DataTemplate
我还尝试了这些示例以在分配给 an时执行相对源绑定,ItemsView
但是当我运行示例时(与上面的示例相同),我会收到一个InvalidOperationException
at:
Xamarin.Forms.Binding.ApplyRelativeSourceBinding (Xamarin.Forms.BindableObject targetObject, Xamarin.Forms.BindableProperty targetProperty) [0x0006c] 在 C:\Advanced Dev\Xamarin.Forms\Xamarin.Forms.Core\Binding.cs:158
转到Xamarin源代码,您可以看到 throw 是由于在. 既然继承自this就是结果。targetObject
Xamarin.Forms.Element
Binding.ApplyRelativeSourceBinding()
EventToCommandBehavior
Xamarin.Forms.Behavior
Xamarin相对绑定文档没有特别提到绑定目标要求,他们显然关注绑定源。但他们确实提到这些绑定搜索可视树或与元素相关:
FindAncestor
表示绑定元素的可视树中的祖先。
Self
指示要在其上设置绑定的元素,
由于 aBehavior
不是Element
并且不是可视树的一部分(它存储在VisualElement.Behaviors
属性中),因此绑定无法直接访问其中任何一个以在运行时执行它的“搜索”,因此绑定永远不会使满意。
我通过扩展 Entry 并在需要的地方添加命令来解决这个问题。这不是最可重用的解决方案,因为我必须在 Switch 等其他元素上执行此操作,但它确实有效。
public class Entry : Xamarin.Forms.Entry
{
public Entry()
{
this.TextChanged += this.OnTextChanged;
}
public static readonly BindableProperty TextChangedCommandProperty =
BindableProperty.Create( nameof( Entry.TextChangedCommand ), typeof( ICommand ), typeof( Entry ) );
public static readonly BindableProperty TextChangedCommandParameterProperty =
BindableProperty.Create( nameof( Entry.TextChangedCommandParameter ), typeof( object ), typeof( Entry ) );
public ICommand TextChangedCommand
{
get => (ICommand)this.GetValue( Entry.TextChangedCommandProperty );
set => this.SetValue( Entry.TextChangedCommandProperty, (object)value );
}
public object TextChangedCommandParameter
{
get => this.GetValue( Entry.TextChangedCommandParameterProperty );
set => this.SetValue( Entry.TextChangedCommandParameterProperty, value );
}
private void OnTextChanged( object sender, TextChangedEventArgs e )
{
if ( this.TextChangedCommand == null ||
!this.TextChangedCommand.CanExecute( this.TextChangedCommandParameter ) )
return;
this.TextChangedCommand.Execute( this.TextChangedCommandParameter );
}
}
并且嵌入在 a 中的 xaml DataTemplate
:
<my:Entry Grid.Column="1"
Text="{Binding Value}"
HorizontalTextAlignment="Start"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center"
VerticalTextAlignment="Center"
Keyboard='Text'
ClearButtonVisibility="WhileEditing"
TextChangedCommand="{Binding BindingContext.TextChangedCommand, Mode=OneTime, Source={RelativeSource FindAncestor, AncestorType={x:Type ItemsView}}}"
TextChangedCommandParameter="{Binding Mode=OneTime}" >
</my:Entry>
更新:
经过几天的实验,我发现了另一种可能的模式来以更通用的方式支持这一点。我会把它留给读者来决定它的优点。我目前倾向于认为它是一个合理的范例并且是通用的,因此没有必要扩展一堆现有的视觉元素。
下面给出的代码模拟了一个观察者的想法,它持有命令并观察它的孩子的事件。Xamarin.Forms.ContentView
父/观察者使用我们在Xamarin 表单示例代码中实现的 Command/CommandParameter/Converter扩展平淡无奇的元素,并将其与 EventName 的附加属性实现相结合。
该ContentView.Content
属性包含一个Xamarin.Forms.View
对象,因此不会混淆 Attached 属性的目标。事件处理程序都是静态的,因此不应该有任何泄漏问题。
public class EventToCommandObserver : ContentView
{
public static readonly BindableProperty EventNameProperty = BindableProperty.CreateAttached( "EventName",
typeof( string ), typeof( View ), null, propertyChanged: OnEventNameChanged );
public static readonly BindableProperty CommandProperty =
BindableProperty.Create( nameof( Command ), typeof( ICommand ), typeof( EventToCommandObserver ) );
public static readonly BindableProperty CommandParameterProperty =
BindableProperty.Create( nameof( CommandParameter ), typeof( object ), typeof( EventToCommandObserver ) );
public static readonly BindableProperty EventArgsConverterProperty =
BindableProperty.Create( nameof( EventArgsConverter ), typeof( IValueConverter ),
typeof( EventToCommandObserver ) );
public ICommand Command
{
get { return (ICommand)this.GetValue( CommandProperty ); }
set { this.SetValue( CommandProperty, value ); }
}
public object CommandParameter
{
get { return this.GetValue( CommandParameterProperty ); }
set { this.SetValue( CommandParameterProperty, value ); }
}
public IValueConverter EventArgsConverter
{
get { return (IValueConverter)this.GetValue( EventArgsConverterProperty ); }
set { this.SetValue( EventArgsConverterProperty, value ); }
}
public static string GetEventName( BindableObject bindable )
{
return (string)bindable.GetValue( EventNameProperty );
}
public static void SetEventName( BindableObject bindable, string value )
{
bindable.SetValue( EventNameProperty, value );
}
private static void OnEventNameChanged( BindableObject bindable, object oldValue, object newValue )
{
DeregisterEvent( oldValue as string, bindable );
RegisterEvent( newValue as string, bindable );
}
private static void RegisterEvent( string name, object associatedObject )
{
if ( string.IsNullOrWhiteSpace( name ) )
{
return;
}
EventInfo eventInfo = associatedObject.GetType().GetRuntimeEvent( name );
if ( eventInfo == null )
{
throw new ArgumentException( $"EventToCommandBehavior: Can't register the '{name}' event." );
}
MethodInfo methodInfo = typeof( EventToCommandObserver ).GetTypeInfo().GetDeclaredMethod( "OnEvent" );
Delegate eventHandler = methodInfo.CreateDelegate( eventInfo.EventHandlerType );
eventInfo.AddEventHandler( associatedObject, eventHandler );
}
private static void DeregisterEvent( string name, object associatedObject )
{
if ( string.IsNullOrWhiteSpace( name ) )
{
return;
}
EventInfo eventInfo = associatedObject.GetType().GetRuntimeEvent( name );
if ( eventInfo == null )
{
throw new ArgumentException( $"EventToCommandBehavior: Can't de-register the '{name}' event." );
}
MethodInfo methodInfo =
typeof( EventToCommandObserver ).GetTypeInfo().GetDeclaredMethod( nameof( OnEvent ) );
Delegate eventHandler = methodInfo.CreateDelegate( eventInfo.EventHandlerType );
eventInfo.RemoveEventHandler( associatedObject, eventHandler );
}
private static void OnEvent( object sender, object eventArgs )
{
if ( ( (View)sender ).Parent is EventToCommandObserver commandView )
{
ICommand command = commandView.Command;
if ( command == null )
{
return;
}
object resolvedParameter;
if ( commandView.CommandParameter != null )
{
resolvedParameter = commandView.CommandParameter;
}
else if ( commandView.EventArgsConverter != null )
{
resolvedParameter =
commandView.EventArgsConverter.Convert( eventArgs, typeof( object ), null, null );
}
else
{
resolvedParameter = eventArgs;
}
if ( command.CanExecute( resolvedParameter ) )
{
command.Execute( resolvedParameter );
}
}
}
}
而这个替代 xaml 嵌入在 a 中DataTemplate
:
<my:EventToCommandObserver Grid.Column="1"
Command="{Binding BindingContext.TextChangedCommand, Mode=OneTime, Source={RelativeSource FindAncestor, AncestorType={x:Type ItemsView}}}"
CommandParameter="{Binding Mode=OneTime}">
<Entry Text="{Binding Value}"
HorizontalTextAlignment="Start"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center"
VerticalTextAlignment="Center"
Keyboard='Text'
ClearButtonVisibility="WhileEditing"
my:EventToCommandObserver .EventName="TextChanged" />
</my:EventToCommandObserver >