首页 > 解决方案 > Xamarin.Forms SwipeView CommandParameter 为 null,除非您重新加载 UI HotReload

问题描述

好的,所以我有一个 CollectionView 绑定到“显示”对象的集合。这个 CollectionView 有很多事情要做。当用户选择一个节目(单击重定向到详细信息页面)时,我有一个命令,选择多个项目以执行多个项目删除,以及滑动以对每个单独的节目执行基本的发布功能。一切正常,除了刷卡。出于某种原因,当触发其中一个滑动命令时,传递给该命令的“显示”对象为空。但是,我为此启用了 HotReload,如果我所做的只是保存 xaml 文件,它工作正常。通过的节目不为空,它按我的预期工作。当我保存 xaml 文件时,我什至不需要更改任何内容,我实际上只是保存而没有新的更改(ctrl + s),并且代码开始工作并且我的滑动项命令很好。

xml:

<ContentPage.ToolbarItems>
        <ToolbarItem IconImageSource="icon_plus.png" Command="{Binding AddShowCommand}" />
        <ToolbarItem Text="Delete" IconImageSource="icon_trash.png" Command="{Binding DeleteMultipleShowsCommand}" CommandParameter="{Binding SelectedShows}" />
    </ContentPage.ToolbarItems>

    <!--
      x:DataType enables compiled bindings for better performance and compile time validation of binding expressions.
      https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/data-binding/compiled-bindings
    -->
    <RefreshView x:DataType="local:ShowsViewModel" Command="{Binding LoadShowsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
        <CollectionView x:Name="ItemsListView"
                ItemsSource="{Binding Shows}"
                SelectionMode="Multiple"
                SelectedItems="{Binding SelectedShows}"
                SelectionChangedCommand="{Binding SelectionChangedCommand}">
            <CollectionView.ItemTemplate>
                <DataTemplate>

                    <SwipeView>
                        <SwipeView.RightItems>
                            <SwipeItems>
                                <SwipeItem Text="Activate"
                                    IconImageSource="favorite.png"
                                    BackgroundColor="LightGreen"
                                    Command="{Binding Source={RelativeSource AncestorType={x:Type local:ShowsViewModel}}, Path=SwipeIsActiveCommand}"
                                    CommandParameter="{Binding}" />
                                <SwipeItem Text="Published"
                                    IconImageSource="delete.png"
                                    BackgroundColor="LightPink"
                                    Command="{Binding Source={RelativeSource AncestorType={x:Type local:ShowsViewModel}}, Path=SwipeIsPublishCommand}"
                                    CommandParameter="{Binding}" />
                                <SwipeItem Text="Sold Out"
                                    IconImageSource="delete.png"
                                    BackgroundColor="LightPink"
                                    Command="{Binding Source={RelativeSource AncestorType={x:Type local:ShowsViewModel}}, Path=SwipeIsSoldOutCommand}"
                                    CommandParameter="{Binding}" />
                            </SwipeItems>
                        </SwipeView.RightItems>
                        <!-- Content -->
                        <Grid BackgroundColor="White">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>

                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="1"/>
                            </Grid.RowDefinitions>

                            <StackLayout Grid.Column="0" Grid.Row="0" Padding="15" x:DataType="model:Show">
                                <Label Text="{Binding ShowTitle}" 
                                   Grid.Row="0"
                                   LineBreakMode="NoWrap" 
                                   Style="{DynamicResource ListItemTextStyle}" 
                                   FontSize="16" />
                                <Label Text="{Binding ShowDate}" 
                                   Grid.Row="1"
                                   LineBreakMode="NoWrap" 
                                   Style="{DynamicResource ListItemTextStyle}" 
                                   FontSize="16" />
                                <StackLayout.GestureRecognizers>
                                    <TapGestureRecognizer 
                                        NumberOfTapsRequired="1"
                                        Command="{Binding Source={RelativeSource AncestorType={x:Type local:ShowsViewModel}}, Path=ShowSelectedCommand}"        
                                        CommandParameter="{Binding}">
                                    </TapGestureRecognizer>
                                </StackLayout.GestureRecognizers>
                            </StackLayout>

                            <BoxView HeightRequest="1"
                               BackgroundColor="Black"
                               Grid.ColumnSpan="2"
                               Grid.Row="1"
                               VerticalOptions="End"/>
                        </Grid>
                        
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup Name="CommonStates">
                                <VisualState Name="Normal" />
                                <VisualState Name="Selected">
                                    <VisualState.Setters>
                                        <Setter Property="BackgroundColor" Value="Yellow" />
                                    </VisualState.Setters>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </SwipeView>

                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </RefreshView>

xml.cs

public partial class ShowsPage : ContentPage
    {
        private ShowsViewModel _viewModel;

        public ShowsPage()
        {
            InitializeComponent();
            BindingContext = _viewModel = new ShowsViewModel();
        }

        protected override void OnAppearing()
        {
            base.OnAppearing();
            _viewModel.OnAppearing();
        }
    }

视图模型:

public class ShowsViewModel : BaseViewModel
    {
        public ObservableCollection<Show> Shows { get; }
        public ObservableCollection<object> SelectedShows { get; }

        public Command SelectionChangedCommand { get; }
        public Command ShowSelectedCommand { get; }
        public Command DeleteMultipleShowsCommand { get; }
        public Command AddShowCommand { get; }
        public Command LoadShowsCommand { get; }
        public Command SwipeIsPublishCommand { get; }
        public Command SwipeIsActiveCommand { get; }
        public Command SwipeIsSoldOutCommand { get; }

        public ShowsViewModel()
        {
            Title = "Shows";
            Shows = new ObservableCollection<Show>();
            SelectedShows = new ObservableCollection<object>();

            LoadShowsCommand = new Command(async () => await LoadItems());
            AddShowCommand = new Command(OnAddItem);
            ShowSelectedCommand = new Command<Show>(OnItemSelected);
            DeleteMultipleShowsCommand = new Command(async () => await DeleteShows(), () => CanDeleteShows());
            SelectionChangedCommand = new Command(DeleteMultipleShowsCommand.ChangeCanExecute);

            SwipeIsPublishCommand = new Command<Show>(async (show) => await PublishShow(show));
            SwipeIsActiveCommand = new Command<Show>(async (show) => await ActivateShow(show));
            SwipeIsSoldOutCommand = new Command<Show>(async (show) => await SoldOutShow(show));
        }

        private async Task LoadItems()
        {
            IsBusy = true;

            try
            {
                Shows.Clear();
                var shows = await DataService.Shows.GetItemsAsync();
                foreach (var show in shows)
                {
                    this.Shows.Add(show);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            finally
            {
                IsBusy = false;
            }
        }

        public void OnAppearing()
        {
            IsBusy = true;
        }

        private async void OnAddItem(object obj)
        {
            await Shell.Current.GoToAsync(nameof(CreateShowPage));
        }

        private async void OnItemSelected(Show show)
        {
            if (show == null)
                return;

            // This will push the ItemDetailPage onto the navigation stack
            await Shell.Current.GoToAsync($"{nameof(ShowDetailPage)}?{nameof(ShowDetailViewModel.ShowId)}={show.ShowId}");
        }

        private bool CanDeleteShows()
        {
            return SelectedShows.Count() > 0;
        }

        private async Task DeleteShows()
        {
            if (SelectedShows != null && SelectedShows.Count() > 0)
            {
                bool confirm = await App.Current.MainPage.DisplayAlert("Delete?", $"Are you sure you want to delete the selected show(s)?", "Yes", "No");
                if (confirm)
                {
                    IsBusy = true;

                    var totalDeleted = 0;
                    foreach (var showobj in SelectedShows)
                    {
                        var show = (Show)showobj;
                        var response = await DataService.Shows.DeleteItemAsync(show.ShowId.ToString());
                        if (response)
                        {
                            totalDeleted++;
                        }
                    }

                    await App.Current.MainPage.DisplayAlert("Delete Results", $"Deleted {totalDeleted} show(s).", "Ok");

                    // This will reload the page
                    IsBusy = true;
                }
            }
        }

        private async Task PublishShow(Show show)
        {
            if (show != null)
            {
                show.IsPublished = !show.IsPublished;
                await DataService.Shows.UpdateItemAsync(show);

                var message = show.IsPublished ? "published" : "unpublished";
                await App.Current.MainPage.DisplayAlert($"Show Updated!", $"Show: {show.ShowTitle} has been {message}", "Ok");
            }
        }

        private async Task ActivateShow(Show show)
        {
            if (show != null)
            {
                show.IsActive = !show.IsActive;
                await DataService.Shows.UpdateItemAsync(show);

                var message = show.IsActive ? "activated" : "archived";
                await App.Current.MainPage.DisplayAlert($"Show Updated!", $"Show: {show.ShowTitle} has been {message}", "Ok");
            }
        }

        private async Task SoldOutShow(Show show)
        {
            if (show != null)
            {
                show.IsSoldOut = !show.IsSoldOut;
                await DataService.Shows.UpdateItemAsync(show);

                var message = show.IsSoldOut ? "sold out" : "not sold out";
                await App.Current.MainPage.DisplayAlert($"Show Updated!", $"Show: {show.ShowTitle} has been marked as {message}", "Ok");
            }
        }
    }

我正在使用带有 USB 调试的 android 物理设备进行测试。我不确定为什么会这样,而且仅仅用 HotReload 重新加载 UI 会改变任何事情似乎很奇怪。

标签: c#xamarinxamarin.forms

解决方案


尝试:

CommandParameter="{Binding Source={RelativeSource Self}, Path=BindingContext}"

以前我使用 SwipeItem Invoked 事件作为解决方法:

private async void PublishedSwipeItem_Invoked(object sender, System.EventArgs e)
{
  var show = (sender as SwipeItem)?.BindingContext as Show;

  if (show!= null)
    await viewModel.PublishShow(show);
}

推荐阅读