如何重置从源 (ViewModel) 到目标 (.xaml) 的绑定

How do I reset the binding from my source (ViewModel) to the target (.xaml)

我有一个 ProfilePage.xaml 包含一些像这样的 ImageButton(其中六个):

<ImageButton
  x:Name="resultImage"
  Source="{Binding Profile.Images[0].Source}"
  Command="{Binding HandleImage}"
  CommandParameter="0">

它们通过我的 ProfilePage.xaml.cs 文件绑定到名为 Profile.cs 的模型,如下所示:

BindingContext = new ViewModels.EditProfilePageViewModel(Profile);

其中 Profile.cs 包含这些图像的列表:

List<Image> images = new List<Image>();
public Profile()
{
    images.Add(new Image());
    images.Add(new Image());
    images.Add(new Image());
    images.Add(new Image());
    images.Add(new Image());
    images.Add(new Image());
}

问题是在某些时候我需要在 ProfilePageViewModel.cs

中使用以下代码进行滑动
for (int i = index; i < Profile.NumberOfImages - 1; i++)
{
    Profile.Images[i].Source = Profile.Images[i + 1].Source;
}

这导致列表项指向下一个,并一起指向最后一个。 我试图使用 new string() 等来避免它,但由于使用以下代码设置 Profile.Images[i].Source 的方式,它没用:

var result = await MediaPicker.PickPhotoAsync();
if (result != null)
{
     var Text = $"File Name: {result.FileName}";
     if (result.FileName.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) ||
            result.FileName.EndsWith("png", StringComparison.OrdinalIgnoreCase) ||
            result.FileName.EndsWith("jpeg", StringComparison.OrdinalIgnoreCase))
     {
            var stream = await result.OpenReadAsync();
            var source = ImageSource.FromStream(() => stream);
            // Add the 'Load Image Icon' on the first available cell
            Profile.Images[Profile.NumberOfImages].Source = source;
            // Increase the number of photos 
            Profile.NumberOfImages++;
     }

我很确定这种加载图像的方式会破坏绑定并造成整个混乱,因为通过硬编码这些图像源的值不会对绑定造成任何问题。所以我提出的问题包括两件事。 关于如何从 MAUI 相机胶卷加载图像的更好解决方案,无需使用 FFImageLoading(不再有效)或重置绑定的方法。


更新:经过几天的努力,我使代码变得简单。

Profile.cs:

public class Profile : INotifyPropertyChanged
{


    public event PropertyChangedEventHandler PropertyChanged;
    /** --------------------------------------------------------------------
    *                           Properties
    *  --------------------------------------------------------------------
    */

    ObservableCollection<ImageSource> imageSources = new ObservableCollection<ImageSource>();
    public ObservableCollection<ImageSource> ImageSources
    {
        get { return imageSources; }
        set { imageSources = value; OnPropertyChanged(nameof(ImageSources)); }
    }
    public Profile()
    {
        
        ImageSources.Add("image1.jpg");
        ImageSources.Add("image2.jpg");
        ImageSources.Add("image3.jpg");
        ImageSources.Add("image4.jpg");
        ImageSources.Add("image5.jpg");
        ImageSources.Add("image6.jpg");
        ImageSources.Add("load_icon.jpg");
      } 
...

编辑ProfilePage.xaml:

    <ScrollView>
    <VerticalStackLayout>

        <!-- Photos Grid -->

        <Grid HorizontalOptions="Center" Padding="30,40" Margin="0,0" RowSpacing="10" ColumnSpacing="10">

            <Grid.RowDefinitions>
                <RowDefinition Height="90"></RowDefinition>
                <RowDefinition Height="90"></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="60"></ColumnDefinition>
                <ColumnDefinition Width="60"></ColumnDefinition>
                <ColumnDefinition Width="60"></ColumnDefinition>
            </Grid.ColumnDefinitions>

            <!-- Image 1 -->

            <ImageButton
                    x:Name="resultImage"
                    Source="{Binding Profile.ImageSources[0]}"
                    Grid.Row="0"
                    Grid.Column="0"
                    Aspect="AspectFill"
                    Grid.RowSpan="1"
                    Grid.ColumnSpan="1"
                    Command="{Binding HandleImage}"
                    CommandParameter="0"

编辑ProfilePage.xaml.cs

 public partial class EditProfilePage : ContentPage
{
  public EditProfilePage(Profile Profile)
  {
    InitializeComponent();
    BindingContext = new 
    ViewModels.EditProfilePageViewModel(Profile);
    
  }
 }

EditProfileViewModel.cs:

    public EditProfilePageViewModel(Profile Profile)
    {
        this.Profile = Profile;
        HandleImage = new Command(OnHandleImage);
    }

    void OnPropertyChanged(string propertyName) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

    void OnHandleImage(object CommandParameter)
    {
        // Get the index of the image
        int index = int.Parse(CommandParameter.ToString());

        // If the index of the selected image is less than the number of the images, delete the selected image
        if (index < NumberOfImages-1)
        {
            DeleteSelectedImage(index);
            
        }
        // In case the ImageButton tapped is the one after the last photo
        else if (index == NumberOfImages-1)
        {
            // Then add a new photo at that posiition
            LoadPhotoFromLibrary();
            
        }
        // In any other case
        else
        {
            // Just do nothing
            return;
        }

        for (int i=0; i<NumberOfImages; i++)
        {
            Console.WriteLine("Image: " + i + " Src:" + Profile.ImageSources[i]);
        }
    }

    void DeleteSelectedImage(int index)
    {
        Profile.ImageSources.RemoveAt(index);
        Profile.ImageSources = Profile.ImageSources;
        Console.WriteLine("Deleted: index" + index  + " Number of Image: " + NumberOfImages);

    }
    async void LoadPhotoFromLibrary()
    {

        // Permsissions
        var status = await CheckAndRequestPhotosPermissionAsync();
        if (status != PermissionStatus.Granted)
        {
            // TODO:Notify user permission was denied
            return;
        }

        
        var result = await MediaPicker.PickPhotoAsync(new MediaPickerOptions
        {
            Title = "Please pick a photo"
        });
        var stream = await result.OpenReadAsync();

        Profile.ImageSources.Insert(NumberOfImages-1, ImageSource.FromStream(() => stream).ToString());
                    
        Profile.ImageSources = Profile.ImageSources;
        Console.WriteLine("Number of Image: " + NumberOfImages);   
    }

截至目前,DeleteSelecteImage() 可以很好地处理硬编码值。每当我从模拟器加载新图像时,该图像通常会显示在视图中,但如果我尝试从该点删除,无论我点击要删除的图像,它都会删除最后一个图像。

我需要指出的是,当我以这种方式从库中上传图片时,出现了以下错误:

[unspecified] container_system_group_path_for_identifier: error = ((container_error_t)98) NOT_CODESIGNED [MC] Error getting system group container for systemgroup.com.apple.configurationprofiles: 98 [MC] Failed to get profile system group container path. Overriding with expected path: /Users/user/Library/Developer/CoreSimulator/Devices/5C228DCC-D446-423B-BE53-EFE37BDE02A6/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles objc[6750]: Class _PathPoint is implemented in both /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore (0x121945650) and /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/TextInputUI.framework/TextInputUI (0x16724d690). One of the two will be used. Which one is undefined. objc[6750]: Class _PointQueue is implemented in both /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore (0x121945628) and /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/TextInputUI.framework/TextInputUI (0x16724d6b8). One of the two will be used. Which one is undefined.

根据您更新后的代码,您可以避免直接访问单个元素 ({Binding Profile.ImageSources[0]}),方法是使用 CollectionView with Grid layout.

EditProfilePage.xaml:

<ScrollView>
    <CollectionView x:Name="myCollection" ItemsSource="{Binding Profile.ImageSources}"
                    ItemsLayout="VerticalGrid, 3"
                    WidthRequest="210" HeightRequest="210" HorizontalOptions="Center" >
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <Grid BackgroundColor="DarkBlue" Padding="5" >
                    <ImageButton Source="{Binding .}"
                                 Command="{Binding BindingContext.HandleImage, Source={x:Reference myCollection}}"
                                 CommandParameter="{Binding .}" />
                </Grid>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
</ScrollView>

public class EditProfileViewModel
{
    ...
    void OnHandleImage(object CommandParameter)
    {
        // Get the index of the image
        var item = (ImageSource)CommandParameter;
        int index = Profile.IndexOf(item);
    }
}


public class Profile : ...
{
    public ObservableCollection<ImageSource> ImageSources { get; set; } = new ObservableCollection<ImageSource>();

    public int IndexOf(ImageSource item)
    {
        return ImageSources.IndexOf(item);
    }
}

解释:

  • ItemsLayout="VerticalGrid, 3" 将 6 张图像排列成一个网格。 (或者 ItemsLayout="HorizontalGrid, 2",它们的顺序不同。)
  • Profile.ImageSourcesImageSource 的集合。
  • Source={x:Reference myCollection} 用于项目绑定,以访问 EditProfileViewModel 上的 HandleImage 属性。 (从页面向下传递到 CollectionView。)
  • Source="{Binding .}" 使用项目本身作为 ImageSource。
  • CommandParameter="{Binding .}" 将项目本身(ImageSource)作为参数传递。
  • 使用IndexOf找到索引。