我已经为此苦苦挣扎了好几天,最后我要向社区询问: 我有一个 .NET MAUI 项目,其中涉及一个在 StackLayout 中包含 BindableLayout 的页面。 BindableLayout 的源数据是我从项目中其他地方引用的 ContentView 组件。视图模型中的数据加载正常,一切都如文档所述。它只是在页面加载时不加载内容。 这是超级有趣的部分: 如果在启用热重载的情况下进行调试时,我退格并替换“.”在行
<reusableViews:BathroomView Content="{Binding .}"/>
然后内容会加载,一切都会按照我的意愿进行。
这是我的父页面的 xaml:
<Grid>
<Grid.RowDefinitions>
<!-- Row 0: Fixed height for the button -->
<RowDefinition Height="*" />
<!-- Row 1: Remaining space for the ScrollView -->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Row 1: ScrollView -->
<ScrollView
x:Name="ScrollView"
Grid.Row="0"
VerticalOptions="FillAndExpand">
<StackLayout>
<!-- Display the BathroomView instances -->
<StackLayout x:Name="BathroomsLayout" BindableLayout.ItemsSource="{Binding BathroomViews}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<!-- Directly render the BathroomView -->
<reusableViews:BathroomView Content="{Binding .}"/>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<StackLayout x:Name="ScrollReference" />
</StackLayout>
</ScrollView>
<AbsoluteLayout Grid.Row="0">
<customControls:LoadingFrame x:Name="NewBathroomLoadingFrame" IsVisible="{Binding IsBusy}" />
</AbsoluteLayout>
<!-- Row 0: Button -->
<Border Grid.Row="1" Margin="0,10,0,10">
<StackLayout Margin="0,10,0,10">
<reusableViews:AdditionalCommentsView />
<Button
Clicked="AddNewBathroom_Clicked"
HorizontalOptions="Center"
Text="Add New Bathroom"
VerticalOptions="Start" />
<Button
x:Name="BathroomsPageSubmitButton"
Clicked="BathroomsPageSubmitButton_Clicked"
Text="Submit Bathrooms Page" />
</StackLayout>
</Border>
</Grid>
父页面的cs:
public partial class BathroomsPage
{
private PageMaster _pageMaster;
private PageFunctions _pageFunctions;
private bool _navigated;
public BathroomsPage(PageMaster pageMaster, PageFunctions pageFunctions)
{
_pageMaster = pageMaster;
_pageFunctions = pageFunctions;
InitializeComponent();
BindingContext ??= _pageMaster.BathroomsPageViewModel;
}
}
pageMaster 和 pageFunctions 是单例并且表现良好。
这是我的 BathroomView xaml:
<ContentView
x:Class="..."
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:buttons="http://schemas.syncfusion.com/maui"
xmlns:customControls="clr-namespace:..."
xmlns:maui="clr-namespace:FFImageLoading.Maui;assembly=FFImageLoading.Maui"
xmlns:reusableViews="clr-namespace:...">
<ContentView.Resources>
<ResourceDictionary>
<Style TargetType="HorizontalStackLayout">
<Setter Property="HorizontalOptions" Value="Center" />
</Style>
<Style TargetType="customControls:ErrorLabel">
<Setter Property="Margin" Value="0,10,0,0" />
</Style>
</ResourceDictionary>
</ContentView.Resources>
<ContentView.Content>
<Frame>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<buttons:SfExpander
x:Name="BathroomsExpander"
Margin="0,15,0,15"
AnimationDuration="150"
ClassId="BathroomsExpander"
HorizontalOptions="Center"
IsExpanded="False"
Expanded="BathroomsExpander_Expanded">
<buttons:SfExpander.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="48" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<HorizontalStackLayout HorizontalOptions="Center">
<Image
Margin="40,0,2,2"
Source="Resources/Images/bath.png"
VerticalOptions="Center"
WidthRequest="40" />
<Label
Grid.Column="1"
Margin="40,0,0,0"
CharacterSpacing="0.25"
FontAttributes="Bold"
FontFamily="Roboto-Regular"
FontSize="14"
HorizontalTextAlignment="Center"
Text="{Binding Bathroom.BathroomName}"
VerticalOptions="CenterAndExpand" />
</HorizontalStackLayout>
</Grid>
</buttons:SfExpander.Header>
<buttons:SfExpander.Content>
<StackLayout Margin="0,10,0,0">
<StackLayout
Margin="0,0,20,0"
HorizontalOptions="Center"
Orientation="Horizontal">
<Label Text="Bathroom Name: " />
<Entry
x:Name="BathOne"
HorizontalTextAlignment="Center"
Text="{Binding Bathroom.BathroomName}"
WidthRequest="175" />
<!--<ImageButton
x:Name="RemoveButton"
Command="{Binding ViewModel.ClearNameCommand}"
CommandParameter="{Binding Bathroom}"
HeightRequest="30"
Source="Resources/Images/remove.png"
WidthRequest="15" />-->
</StackLayout>
<customControls:ErrorLabel Name="BathroomNameError" Text="Please give a name to this bathroom" />
<Label
FontAttributes="Bold"
HorizontalTextAlignment="Center"
Text="GFCI Outlets?" />
<StackLayout>
<HorizontalStackLayout>
<RadioButton Content="Yes" IsChecked="{Binding Bathroom.GfciOutletsYes}" />
<RadioButton Content="No" IsChecked="{Binding Bathroom.GfciOutletsNo}" />
<RadioButton Content="No Outlet" IsChecked="{Binding Bathroom.GfciNoOutlet}" />
</HorizontalStackLayout>
<StackLayout x:Name="GfciFuncNonFuncLayout" IsVisible="{Binding Bathroom.GfciOutletsYes}">
<HorizontalStackLayout HorizontalOptions="Center">
<RadioButton Content="Functional" IsChecked="{Binding Bathroom.GfciFunc}" />
<RadioButton Content="Non-Functional" IsChecked="{Binding Bathroom.GfciNonFunc}" />
</HorizontalStackLayout>
<StackLayout HorizontalOptions="Center" IsVisible="{Binding Bathroom.GfciNonFunc}">
<Label HorizontalTextAlignment="Center" Text="Please describe the issue with the outlet:" />
<Entry Text="{Binding Bathroom.GfciNonFuncEntry}" />
<customControls:ErrorLabel Name="GfciNonFuncEntryError" Text="Please detail the problem with the outlet" />
</StackLayout>
<customControls:ErrorLabel Name="GfciFuncNonFuncError" Text="Please select functional or non-functional" />
</StackLayout>
<customControls:ErrorLabel Name="GfciYesNoNoOutletError" Text="Please select an option for the GFCI outlets" />
</StackLayout>
<StackLayout Margin="40,0,40,0">
<Border
Margin="{OnPlatform Default='8,0,8,8',
WinUI='8,0,6,8',
MacCatalyst='8,0,6,8'}"
Stroke="#CAC4D0"
StrokeShape="RoundRectangle 8,8,8,8"
StrokeThickness="{OnPlatform MacCatalyst=2,
Default=1}"
WidthRequest="{OnPlatform MacCatalyst=460,
WinUI=340}">
<buttons:SfExpander AnimationDuration="150">
<buttons:SfExpander.Header>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="48" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image
Margin="14,2,2,2"
Source="Resources/Images/commentplus.png"
VerticalOptions="Center" />
<Label
Grid.Column="1"
Margin="10,0,0,0"
CharacterSpacing="0.25"
FontFamily="Roboto-Regular"
FontSize="14"
Text="Additional Comments"
VerticalOptions="CenterAndExpand" />
</Grid>
</buttons:SfExpander.Header>
<buttons:SfExpander.Content>
<Editor
Margin="20"
HeightRequest="200"
Placeholder="Enter any additional info here"
Text="{Binding Bathroom.AdditionalCommentsText}" />
</buttons:SfExpander.Content>
</buttons:SfExpander>
</Border>
<Border>
<StackLayout Margin="0,20,0,20">
<HorizontalStackLayout>
<Image
Margin="10"
Source="Resources/Images/imagesicon.png"
WidthRequest="30" />
<Label
FontAttributes="Bold"
FontSize="Large"
HorizontalTextAlignment="Center"
Text="Images" />
</HorizontalStackLayout>
<!-- UPDATE NAME OF BUTTON -->
<Button
x:Name="AddBathroomPhotoButton"
Clicked="AddBathroomPhotoButton_Clicked"
Text="Add Photo"
WidthRequest="150" />
<StackLayout x:Name="ImagesLayout" />
</StackLayout>
</Border>
</StackLayout>
</StackLayout>
</buttons:SfExpander.Content>
</buttons:SfExpander>
<StackLayout Grid.Row="1">
<Button
Command="{Binding ViewModel.RemoveCommand}"
CommandParameter="{Binding Bathroom}"
Text="Remove Bathroom" />
</StackLayout>
</Grid>
</Frame>
</ContentView.Content>
</ContentView>
还有 BathroomView.xaml.cs:
using ECHI_Inspection_App_Maui.CustomControls;
using ECHI_Inspection_App_Maui.Helpers;
using Echi_Inspection_App_Maui.Models;
using Echi_Inspection_App_Maui.ViewModels.InspectionPageViewModels;
using Microsoft.Maui.Controls.Compatibility;
using Newtonsoft.Json;
using SQLite;
using Guid = System.Guid;
using FFImageLoading.Maui;
using StackLayout = Microsoft.Maui.Controls.StackLayout;
using System.Collections.ObjectModel;
namespace Echi_Inspection_App_Maui.Reusable_Views;
public partial class BathroomView
{
public static readonly BindableProperty ViewModelProperty =
BindableProperty.Create(nameof(ViewModel), typeof(BathroomsPageViewModel), typeof(BathroomView));
public static readonly BindableProperty BathroomModelProperty =
BindableProperty.Create(nameof(Bathroom), typeof(Bathroom), typeof(BathroomView));
public BathroomsPageViewModel ViewModel
{
get => (BathroomsPageViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public Bathroom Bathroom
{
get => (Bathroom)GetValue(BathroomModelProperty);
set => SetValue(BathroomModelProperty, value);
}
public BathroomView()
{
InitializeComponent();
}
private void DeletePhotoButton_Clicked(object? sender, EventArgs e)
{
if (sender is not Button button) return;
// Get the parent StackLayout that contains both the CachedImage and the Button
if (button.Parent is not StackLayout parentLayout) return;
// Find the CachedImage within the parent StackLayout
if (parentLayout.Children.FirstOrDefault(c => c is CachedImage) is not CachedImage cachedImage) return;
// Remove the image source from your data collection
var imageUrl = cachedImage.Source?.ToString();
if (imageUrl != null)
{
Bathroom.Images.Remove(imageUrl);
// Perform any additional cleanup or logic related to the image removal
PageFunctions.DeletePhoto(imageUrl);
}
// Remove the CachedImage and the Button from the parent StackLayout
parentLayout.Children.Remove(cachedImage);
parentLayout.Children.Remove(button);
}
private async void AddBathroomPhotoButton_Clicked(object sender, EventArgs e)
{
var uploadedUrl = await PageFunctions.CapturePhotoAndGetUrl($"Interior WCF {Guid.NewGuid()}.jpg");
if (uploadedUrl == null) return;
Bathroom.Images.Add(uploadedUrl); //UPDATE THIS
var deleteButton = new Button()
{
Text = "Delete",
WidthRequest = 100,
ClassId = "ImageButton",
};
deleteButton.Clicked += DeletePhotoButton_Clicked;
ImagesLayout.Children.Add(new StackLayout()
{
Children =
{
new CachedImage()
{
Source = uploadedUrl,
Aspect = Aspect.Center,
WidthRequest = 200
},
deleteButton
}
});
}
private static void AddImagesToStackLayout(StackLayout layout, ObservableCollection<string> images)
{
foreach (var image in images)
{
layout.Children.Add(new StackLayout()
{
Children =
{
new CachedImage()
{
Source = image,
Aspect = Aspect.Center,
WidthRequest = 200
},
new Button()
{
Text = "Delete",
WidthRequest = 100,
ClassId = "ImageButton"
}
}
});
}
}
private void BathroomsExpander_Expanded(object sender, Syncfusion.Maui.Expander.ExpandedAndCollapsedEventArgs e)
{
Bathroom.Images ??= [];
if (!Bathroom.Images.Any()) return; //UPDATE THIS
AddImagesToStackLayout(ImagesLayout, Bathroom.Images);
var imageButtons = this.GetVisualTreeDescendants().OfType<Button>().Where(x => x.ClassId == "ImageButton")
.ToList();
foreach (var imageButton in imageButtons)
{
imageButton.Clicked += DeletePhotoButton_Clicked;
}
}
}
BathroomsExpander_Expanded() 是为了尝试加载 ContentView 底部的图像,因为另一个问题我不会在这里讨论。
重申一下有趣的部分:我可以删除并替换“.”在
<reusableViews:BathroomView Content="{Binding .}"/>
一切都呈现良好。我认为这是最大的线索。它要么与渲染与数据的顺序有关,要么是框架中的错误,要么是我没有正确使用它。
应该直接使用 Angular
首先,
BathroomView
?有史以来最酷的页面名称!
在这里,您创建了
BathroomView
的新实例,并将其内容设置为 ItemsSource 的项目,这又是 BathroomView
的对象。
这并不理想。您可以定义一个新的
ContentView
并将其 Content
设置为您的 BathroomView
对象。就像下面这样
<DataTemplate>
<!-- Directly render the BathroomView -->
<ContentView Content="{Binding .}"/>
</DataTemplate>