Home > database >  What is the correct way to use RelativeSource in the outer tag?
What is the correct way to use RelativeSource in the outer tag?

Time:11-03

I want to avoid repeating Source={RelativeSource AncestorType={x:Type vm:MainViewModel}} in the following.

<SwipeView ...   xmlns:vm="clr-namespace:Todo.ViewModel">
    <SwipeView.LeftItems>
        <SwipeItems>
            <SwipeItem Text="Delete" 
                Command="{Binding DeleteCommand,Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}}" />
        </SwipeItems>
    </SwipeView.LeftItems>
    <Grid Padding="0,5">
        <Frame >
            <Frame.GestureRecognizers>
                <TapGestureRecognizer
                Command="{Binding TapCommand,Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}}"/>
            </Frame.GestureRecognizers>
        </Frame>
    </Grid>
</SwipeView>

I do the following but it does not work as expected.

<SwipeView ... xmlns:vm="clr-namespace:Todo.ViewModel"
    BindingContext="{Binding Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}}"
        >
    <SwipeView.LeftItems>
        <SwipeItems>
            <SwipeItem Text="Delete"  Command="{Binding DeleteCommand}" />
        </SwipeItems>
    </SwipeView.LeftItems>
    <Grid Padding="0,5">
        <Frame >
            <Frame.GestureRecognizers>
                <TapGestureRecognizer  Command="{Binding TapCommand}" />
            </Frame.GestureRecognizers>
        </Frame>
    </Grid>
</SwipeView>

Repo

https://github.com/pstricks-fans/Todo

Here are the relevant parts:

MySwipeView:

public partial class MySwipeView : SwipeView
{
    public MySwipeView()
    {
        InitializeComponent();
    }
}
<SwipeView ...
    x:Class="Todo.CustomControls.MySwipeView"
    xmlns:vm="clr-namespace:Todo.ViewModel"
    >
    <SwipeView.LeftItems>
        <SwipeItems>
            <SwipeItem 
                Text="Delete" 
                Command="{Binding DeleteCommand,Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}}"
                CommandParameter="{Binding .}"/>
        </SwipeItems>
    </SwipeView.LeftItems>
    <Grid Padding="0,5">
        <Frame >
            <Frame.GestureRecognizers>
                <TapGestureRecognizer
                Command="{Binding TapCommand,Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}}"
                CommandParameter="{Binding .}"/>
            </Frame.GestureRecognizers>
            <Label Text="{Binding .}" FontSize="24"/>
        </Frame>
    </Grid>
</SwipeView>

MainPage:

public partial class MainPage : ContentPage
{
    public MainPage(MainViewModel vm)
    {
        InitializeComponent();
        BindingContext = vm;
    }
}
<ContentPage ...
             xmlns:local="clr-namespace:Todo.CustomControls"
             xmlns:vm="using:Todo.ViewModel"
             x:DataType="vm:MainViewModel"
             >

    <Grid ... >


        <CollectionView ...  >
            <CollectionView.ItemTemplate>
                <DataTemplate x:DataType="{x:Type x:String}">
                    <local:MySwipeView />
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </Grid>

</ContentPage>

MainViewModel:

public partial class MainViewModel : ObservableObject
{
    [RelayCommand]
    void Delete(string s){}

    [RelayCommand]
    async Task Tap(string s){}
}

CodePudding user response:

In your case, SwipeView is being used inside an ItemTemplate. You MUST NOT change its BindingContext; that has to be the associated Item.

Therefore, your original goal is NOT POSSIBLE; we can simplify the "Source" expression, but we cannot eliminate it.

Simplest I know is:

Command="{Binding VM.DeleteCommand, Source={x:Reference thePage}}"

Explanation: On "thePage", finds property "VM", which contains a property "DeleteCommand". Make the following changes to MainPage.

<ContentPage
   ...
   x:Name="thePage"
   x:Class="MainPage">
class MainPage : ContentPage
{
  // You can change this name. Be sure to use same name in XAML above.
  public property MainViewModel VM { get; set; }

  public MainPage(MainViewModel vm)
  {
    InitializeComponent();

    // Put this line BEFORE set BindingContext. Used by XAML.
    VM = vm;
    BindingContext = vm;
  }

CodePudding user response:

At first, the format of binding to an ancestor in the official document is something like {Binding Source={RelativeSource AncestorType={x:Type local:PeopleViewModel}}, Path=DeleteEmployeeCommand}

And then you can try to set the SwipeView's binding context in the construction method instead of the binding way you used.

I have done a sample to test, and the binding worked well:

The MySwipeView.cs :

    public partial class MySwipeView : SwipeView
    {
      public ICommand TestCommand { get; private set; }
      public MySwipeView()
      {
            InitializeComponent();
            TestCommand = new Command<string>(Test);
            //BindingContext = this;
            BindingContext = new MyViewModel();
      }
      void Test(string print)
      {
            Debug.WriteLine("============" print);
      }

    }

The MySwipeView.xaml:

<SwipeView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiAppTest.MySwipeView"
             >
    <SwipeView.LeftItems>
        <SwipeItems>
            <SwipeItem
                Text="Delete"
                Command="{Binding DeleteCommand}"
                CommandParameter="xxxxxxxxx"/>
        </SwipeItems>
    </SwipeView.LeftItems>
    <Grid Padding="0,5">
        <Frame >
            <Frame.GestureRecognizers>
                <TapGestureRecognizer
                Command="{Binding TapCommand}"
                CommandParameter="xxxxxxxx"/>
            </Frame.GestureRecognizers>
            <Label Text="xxxxxxxx" FontSize="24"/>
        </Frame>
    </Grid>
</SwipeView>

The MainPage.xaml:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MauiApp21"
             x:Class="MauiApp21.MainPage">

    <HorizontalStackLayout>
        <local:MySwipeView/>
    </HorizontalStackLayout>

</ContentPage>

The MyViewModel.cs:

public partial class MyViewModel : ObservableObject
    {
        [RelayCommand]
        void Delete(string value) { Debug.WriteLine("===========Delete"); }
        [RelayCommand]
        void Tap(string value) { Debug.WriteLine("===============Tap"); }
    }

No matter the BindingContext is this or the MyViewModel, the command will run successfully.

  • Related