1

在 CollectionView 中使用 DataTemplates... 我可以从这样的按钮调用 ViewModel 的命令:

<Button Text="Test"
    Command="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}},
                      Path=BindingContext.TestCommand}"/>

或者像这样的手势:

<Frame.GestureRecognizers>
    <TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}}, Path=BindingContext.TestCommand}"/>        
</Frame.GestureRecognizers>

那么,为什么我不能像这样从条目的 TextChanged 事件中调用该命令呢?

<Entry x:Name="PortionEntry"
    Text ="{Binding QtyTest, Mode=TwoWay}">
    <Entry.Behaviors>
        <behavors:EventToCommandBehavior
            EventName="TextChanged"
            Command="{Binding Source={RelativeSource AncestorType={x:Type ContentPage}},
                              Path=BindingContext.TestCommand}"/>
    </Entry.Behaviors>  

EventToCommandBehavior的代码在 DataTemplate 中不使用时有效

这是一个说明问题的项目: https ://github.com/BullCityCabinets/DataTemplateEventIssue

我从这些优秀的人那里得到了按钮代码: https ://www.syncfusion.com/kb/11029/how-to-bind-command-from-viewmodel-to-external-itemtemplate-of-xamarin-forms-listview

谢谢!

4

2 回答 2

0

我测试你的代码,如果我不使用相对源绑定,它可以正常工作。

<ContentPage
x:Class="demo3.simplecontrol2.Page2"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:customcontrol="clr-namespace:demo3.customcontrol"
xmlns:local="clr-namespace:demo3.simplecontrol2"
x:Name="root">
<ContentPage.Resources>
    <DataTemplate x:Key="datatemplate1">
        <StackLayout>
            <Entry Text="{Binding str, Mode=TwoWay}">
                <Entry.Behaviors>
                    <local:EventToCommandBehavior Command="{Binding Source={x:Reference root}, Path=BindingContext.command1}" EventName="TextChanged" />
                </Entry.Behaviors>
            </Entry>
        </StackLayout>

    </DataTemplate>
</ContentPage.Resources>
<ContentPage.Content>
    <StackLayout>
       

        <Entry Text="123">
            <Entry.Behaviors>
                <local:EventToCommandBehavior Command="{Binding command1}" EventName="TextChanged" />
            </Entry.Behaviors>
        </Entry>

        <CollectionView ItemTemplate="{StaticResource datatemplate1}" ItemsSource="{Binding entries}">
           
        </CollectionView>


      
    </StackLayout>
</ContentPage.Content>
 public partial class Page2 : ContentPage
{
    public ObservableCollection<testentry> entries { get; set; }

    
   public Command command1 { get; set; }

  
    public Page2()
    {
        InitializeComponent();

        entries = new ObservableCollection<testentry>()
        {
            new testentry(){str="test 1"},
            new testentry(){str="test 2"},
            new testentry(){str="test 3"},
            new testentry(){str="test 4"},
            new testentry(){str="test 5"},
            new testentry(){str="test 6"}

        };

        command1 = new Command(testcommand);
       
        

        this.BindingContext = this;

       
    }
   
    private void testcommand()
    {
       
        Console.WriteLine("this is test!");
    }

   
}
于 2020-10-27T08:16:51.443 回答
0

我查看了您的示例代码,您似乎正在使用Xamarin 表单示例代码来实现您的EventToCommandBehavior. 这也在Xamarin 社区工具包中以大致相同的方式实现。请注意,这些实现继承自Xamarin.Forms.Behavior.

DataTemplate我还尝试了这些示例以在分配给 an时执行相对源绑定,ItemsView但是当我运行示例时(与上面的示例相同),我会收到一个InvalidOperationExceptionat:

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就是结果。targetObjectXamarin.Forms.ElementBinding.ApplyRelativeSourceBinding()EventToCommandBehaviorXamarin.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 >
于 2021-03-19T15:46:12.057 回答