如何在外层标签正确使用RelativeSource避免重复绑定ViewModel?
解决方案
要解决重复编写Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}绑定代码的问题,核心是保留SwipeView的BindingContext为集合项(保证{Binding .}能正常获取当前列表项),同时让命令绑定到上级的MainViewModel,避免重复代码。
方法一:通过x:Reference直接绑定到MainPage的BindingContext
先在MainPage的XAML里给根元素命名:
<ContentPage ... x:Name="MainPageRoot" xmlns:local="clr-namespace:Todo.CustomControls" xmlns:vm="using:Todo.ViewModel" x:DataType="vm:MainViewModel" > <!-- 其余内容不变 --> </ContentPage>
然后修改自定义MySwipeView的XAML,通过引用MainPage的BindingContext绑定命令:
<SwipeView ... x:Class="Todo.CustomControls.MySwipeView" xmlns:vm="clr-namespace:Todo.ViewModel" > <SwipeView.LeftItems> <SwipeItems> <SwipeItem Text="Delete" Command="{Binding BindingContext.DeleteCommand, Source={x:Reference MainPageRoot}}" CommandParameter="{Binding .}"/> </SwipeItems> </SwipeView.LeftItems> <Grid Padding="0,5"> <Frame > <Frame.GestureRecognizers> <TapGestureRecognizer Command="{Binding BindingContext.TapCommand, Source={x:Reference MainPageRoot}}" CommandParameter="{Binding .}"/> </Frame.GestureRecognizers> <Label Text="{Binding .}" FontSize="24"/> </Frame> </Grid> </SwipeView>
方法二:给自定义控件添加绑定属性(推荐)
这种方式符合MVVM规范,将命令作为控件的依赖属性从外部传入:
- 修改MySwipeView后台代码,添加依赖属性:
public partial class MySwipeView : SwipeView { public static readonly BindableProperty DeleteCommandProperty = BindableProperty.Create(nameof(DeleteCommand), typeof(ICommand), typeof(MySwipeView)); public ICommand DeleteCommand { get => (ICommand)GetValue(DeleteCommandProperty); set => SetValue(DeleteCommandProperty, value); } public static readonly BindableProperty TapCommandProperty = BindableProperty.Create(nameof(TapCommand), typeof(ICommand), typeof(MySwipeView)); public ICommand TapCommand { get => (ICommand)GetValue(TapCommandProperty); set => SetValue(TapCommandProperty, value); } public MySwipeView() { InitializeComponent(); } }
- 修改MySwipeView的XAML,绑定到自身的依赖属性:
<SwipeView ... x:Class="Todo.CustomControls.MySwipeView" xmlns:vm="clr-namespace:Todo.ViewModel" x:Name="ThisSwipeView" > <SwipeView.LeftItems> <SwipeItems> <SwipeItem Text="Delete" Command="{Binding DeleteCommand, Source={x:Reference ThisSwipeView}}" CommandParameter="{Binding .}"/> </SwipeItems> </SwipeView.LeftItems> <Grid Padding="0,5"> <Frame > <Frame.GestureRecognizers> <TapGestureRecognizer Command="{Binding TapCommand, Source={x:Reference ThisSwipeView}}" CommandParameter="{Binding .}"/> </Frame.GestureRecognizers> <Label Text="{Binding .}" FontSize="24"/> </Frame> </Grid> </SwipeView>
- 在MainPage的CollectionView中使用控件时,传入MainViewModel的命令:
<CollectionView ... > <CollectionView.ItemTemplate> <DataTemplate x:DataType="{x:Type x:String}"> <local:MySwipeView DeleteCommand="{Binding DeleteCommand, Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}}" TapCommand="{Binding TapCommand, Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}}" /> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView>
方法三:复用RelativeSource绑定资源
如果不想修改后台代码,可以在自定义控件中定义一个绑定资源,复用RelativeSource配置:
<SwipeView ... x:Class="Todo.CustomControls.MySwipeView" xmlns:vm="clr-namespace:Todo.ViewModel" > <SwipeView.Resources> <Binding x:Key="MainVmBinding" Source="{RelativeSource AncestorType={x:Type vm:MainViewModel}}"/> </SwipeView.Resources> <SwipeView.LeftItems> <SwipeItems> <SwipeItem Text="Delete" Command="{Binding DeleteCommand, Source={StaticResource MainVmBinding}}" CommandParameter="{Binding .}"/> </SwipeItems> </SwipeView.LeftItems> <Grid Padding="0,5"> <Frame > <Frame.GestureRecognizers> <TapGestureRecognizer Command="{Binding TapCommand, Source={StaticResource MainVmBinding}}" CommandParameter="{Binding .}"/> </Frame.GestureRecognizers> <Label Text="{Binding .}" FontSize="24"/> </Frame> </Grid> </SwipeView>
为什么直接设置SwipeView的BindingContext无效?
SwipeView处于CollectionView的ItemTemplate中,默认BindingContext是当前的集合项(此处为string类型的列表项)。如果直接给SwipeView设置BindingContext为MainViewModel,会覆盖默认值,导致CommandParameter="{Binding .}"和Label Text="{Binding .}"无法获取到当前列表项的内容,因此这种方法不可行。
内容的提问来源于stack exchange,提问作者D G
相关产品推荐
相关产品推荐

