如何重置从源 (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.ImageSources
是 ImageSource
的集合。
Source={x:Reference myCollection}
用于项目绑定,以访问 EditProfileViewModel 上的 HandleImage
属性。 (从页面向下传递到 CollectionView。)
Source="{Binding .}"
使用项目本身作为 ImageSource。
CommandParameter="{Binding .}"
将项目本身(ImageSource)作为参数传递。
- 使用
IndexOf
找到索引。
我有一个 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.ImageSources
是ImageSource
的集合。Source={x:Reference myCollection}
用于项目绑定,以访问 EditProfileViewModel 上的HandleImage
属性。 (从页面向下传递到 CollectionView。)Source="{Binding .}"
使用项目本身作为 ImageSource。CommandParameter="{Binding .}"
将项目本身(ImageSource)作为参数传递。- 使用
IndexOf
找到索引。