我查看了您的示例代码,您似乎正在使用Xamarin Forms Example code 来实现您的EventToCommandBehavior。这也在Xamarin Community Toolkit 中以大致相同的方式实现。 注意这些实现继承自Xamarin.Forms.Behavior。
我还尝试使用这些示例在分配给ItemsView 的DataTemplate 中执行相对源绑定,但是当我运行示例时(与您上面的示例相同),我会在以下位置收到InvalidOperationException:
Xamarin.Forms.Binding.ApplyRelativeSourceBinding
(Xamarin.Forms.BindableObject 目标对象,
Xamarin.Forms.BindableProperty targetProperty) [0x0006c] 在
C:\Advanced Dev\Xamarin.Forms\Xamarin.Forms.Core\Binding.cs:158
转到Xamarin source code,您可以看到在Binding.ApplyRelativeSourceBinding() 中应用绑定时,抛出是绑定targetObject 没有从Xamarin.Forms.Element 继承的结果。由于EventToCommandBehavior 继承自Xamarin.Forms.Behavior,结果就是这样。
Xamarin Relative Binding docs 没有特别提到绑定 Target 要求,他们显然与绑定 Source 相关。但他们确实提到这些绑定搜索可视树或与元素相关:
FindAncestor 表示绑定元素的可视树中的祖先。
Self 表示要设置绑定的元素,
由于 Behavior 不是 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 );
}
}
而 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 Example code 中实现的Command/CommandParameter/Converter 扩展了乏味的Xamarin.Forms.ContentView 元素,并将其与EventName 的Attached Property 实现结合起来。
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 嵌入在 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 >