You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin.Forms从代码后台访问ImagePickerViewModel始终返回null

问题描述

我正在学习Xamarin.Forms,目前开发了一款可调用相机拍照、将照片显示在FlowListView中的控件,最终希望将这些照片转为邮件附件发送。该控件的相册展示功能正常,邮件功能由他人编写,但我无法从邮件页面(EmailCreatePage)的代码后台访问该控件的ImagePickerViewModel:每次尝试获取AttachmentList的内容时都返回null,即使不在邮件代码后台初始化ImagePickerViewModel,结果依然如此。请问正确的实现方式是什么?


ImagePickerViewModel.cs

[XamlCompilation(XamlCompilationOptions.Compile)]
public class ImagePickerViewModel : BindableObject, INotifyPropertyChanged
{
    // HERE WOULD BE A TON OF DEPENDENCIES

    public ImagePickerViewModel()
    {
        // Commands
        TakePictureCmd = new Command(TakePicture);

        //ObservableCollection
        TakenPicturesList = new ObservableCollection<Model.ImagePicker>();
        AttachmentList = new List<BaseObject>();   
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public Model.ImagePicker NewPicture { get; set; }

    public BaseObject attachment { get; set; }
    public List<BaseObject> AttachmentList { get; set; }

    public ObservableCollection<Model.ImagePicker> TakenPicturesList { get; set; }

    public string selectedImage { get; set; }
    public string Path
    {
        get { return NewPicture.ImagePath; }
        set { NewPicture.ImagePath = value; OnPropertyChanged(nameof(Path)); }
    }

    public Command TakePictureCmd { get; set; }


    private async void TakePicture()
    {
        var path = await _cameraService.TakePictureAsync();
        TakenPicturesList.Add(new Model.ImagePicker { ImagePath = path });
        GetFilesForAttachment(path);
    }


    private void GetFilesForAttachement(string filePath)
    {
        attachment = Attachment.FromFile(filePath);
        if (attachment != null)
        {
            AttachmentList.Add(attachment);
        }
    }

    public void RaisedOnPropertyChanged(string _PropertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(_PropertyName));
    }
}

ImagePickerView.xaml

<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
             xmlns:flv="clr-namespace:DLToolkit.Forms.Controls;assembly=DLToolkit.Forms.Controls.FlowListView"
             xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
             xmlns:local="clr-namespace:SolIT.Mobile.Client" 
             xmlns:vm="clr-namespace:SolIT.Mobile.Client.Features.Shared.Controls.ImagePicker.ViewModel"
             xmlns:buttons="clr-namespace:Syncfusion.XForms.Buttons;assembly=Syncfusion.Buttons.XForms"
             x:Class="SolIT.Mobile.Client.Features.Shared.Controls.ImagePicker.View.ImagePickerView"
             x:Name="View_ImagePickerView">
    <ContentView.BindingContext>
        <vm:ImagePickerViewModel ContextView="{x:Reference View_ImagePickerView}"/>
    </ContentView.BindingContext>

    <Frame>
        <ContentView.Content>
            <StackLayout>

                <Grid 
          ColumnSpacing="0"
          HorizontalOptions="CenterAndExpand">

                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>

                    <buttons:SfButton
            x:Name="Btn_takePicture"
            Grid.Column="1"
            Text="{local:Translate Common.SelectFilesPage.TakePicture}"
            TextColor="Black"
            ImageSource="{Binding TakePictureIcon}"
            ShowIcon="true"
            BackgroundColor="Transparent"
            ImageAlignment="Top"
            HorizontalOptions="FillAndExpand"
            Margin="3"
            CornerRadius="5"
            Command="{Binding TakePictureCmd}"/>
                </Grid>

                <BoxView Grid.ColumnSpan="2" Grid.Row="1" HeightRequest="1" BackgroundColor="{StaticResource Win10Devider}"/>

                <flv:FlowListView x:Name="pictureFlowListView" SeparatorVisibility="None" HasUnevenRows="true"
            FlowColumnMinWidth="110" FlowItemsSource="{Binding TakenPicturesList}">
                                  <!--FlowItemTappedCommand="{Binding PictureTappedCmd}"
                                  FlowLastTappedItem="{Binding SelectedItem}"-->
                    <flv:FlowListView.FlowColumnTemplate>
                        <DataTemplate> 
                            <Grid Padding="3">
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="*" />
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <StackLayout>
                                <ffimageloading:CachedImage HeightRequest="100" Aspect="AspectFill"
                            DownsampleHeight="100" DownsampleUseDipUnits="false" x:Name="imageCached"
                            Source="{Binding ImagePath}">
                                    <ffimageloading:CachedImage.GestureRecognizers>
                                        <TapGestureRecognizer Command="{Binding Path=BindingContext.PictureTappedCmd, Source={x:Reference pictureFlowListView}}" CommandParameter="{x:Reference imageCached}"></TapGestureRecognizer>
                                    </ffimageloading:CachedImage.GestureRecognizers>
                                </ffimageloading:CachedImage>
                                </StackLayout>
                            </Grid>
                        </DataTemplate>
                    </flv:FlowListView.FlowColumnTemplate>
                </flv:FlowListView>
            </StackLayout>
        </ContentView.Content>
    </Frame>
</ContentView>

EmailCreatePage.xaml.cs

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EmailCreatePage: ContentPage
{
    // HERE WOULD BE A TON OF DEPENDENCIES

    public EmailCreateViewModel ViewModel { get; }
    public ImagePickerViewModel ViewModelImagePicker {get;}

    public EmailCreatePage()
    {
        InitializeComponent();
        BindingContext = ViewModel = new EmailCreateViewModel();
        ViewModelImagePicker = new ImagePickerViewModel();
    }
private void CreateAttachment()
    {
 
            var attachmentList = GetFileList();
            foreach (var file in attachmentList)
            {
                var attachment = Attachment.FromFile(file.ToString());
                if (attachment != null)
                {

                    ViewModel.CubesMessage.ConnectedObjects.Add(attachment);
                }
        }

public List<BaseObject> GetFileList()
    {
// GET LIST FROM OTHER VIEWMODEL HERE
// VIEWMODEL IS ALWAYS NULL
          var attachmentList = ImagePickerViewModel.AttachmentList;
          return attachmentList;
}

// HERE WOULD BE THE CODE THAT SENDS THE EMAIL OFF AFTER A BUTTON CLICK

解决方案

问题根源

你当前的代码里,EmailCreatePage中新建的ImagePickerViewModel实例和ImagePickerView内部创建的ViewModel实例是完全独立的两个对象,所以前者永远拿不到后者的AttachmentList数据。另外注意:TakePicture方法调用的GetFilesForAttachment和实际定义的GetFilesForAttachement存在拼写错误,这会导致照片根本不会被添加到AttachmentList中,先修正这个拼写问题。

实现步骤

步骤1:修改ImagePickerView,支持外部传入ViewModel

首先移除XAML中硬编码的ViewModel实例,改为通过绑定接收外部传入的实例:

<!-- 删除以下硬编码内容 -->
<!-- <ContentView.BindingContext>
    <vm:ImagePickerViewModel ContextView="{x:Reference View_ImagePickerView}"/>
</ContentView.BindingContext> -->

然后在ImagePickerView后台代码中添加ViewModel绑定属性:

public partial class ImagePickerView : ContentView
{
    public static readonly BindableProperty ViewModelProperty = BindableProperty.Create(
        nameof(ViewModel),
        typeof(ImagePickerViewModel),
        typeof(ImagePickerView));

    public ImagePickerViewModel ViewModel
    {
        get => (ImagePickerViewModel)GetValue(ViewModelProperty);
        set => SetValue(ViewModelProperty, value);
    }

    public ImagePickerView()
    {
        InitializeComponent();
        BindingContext = this;
    }
}

同时修改ImagePickerView内部的绑定路径,比如按钮命令和列表数据源:

<!-- 按钮Command绑定 -->
Command="{Binding ViewModel.TakePictureCmd}"

<!-- FlowListView数据源绑定 -->
FlowItemsSource="{Binding ViewModel.TakenPicturesList}"

步骤2:在EmailCreateViewModel中共享同一个ImagePickerViewModel实例

修改EmailCreateViewModel,添加共享的ImagePickerViewModel属性:

public class EmailCreateViewModel : BindableObject
{
    public ImagePickerViewModel ImagePickerVm { get; }

    public EmailCreateViewModel()
    {
        ImagePickerVm = new ImagePickerViewModel();
    }

    // 原有代码...
}

然后在EmailCreatePage.xaml中,将ImagePickerView绑定到这个共享实例:

<local:ImagePickerView ViewModel="{Binding ImagePickerVm}" />

步骤3:在EmailCreatePage中获取正确的AttachmentList

修改GetFileList方法,从共享的ViewModel实例中获取数据:

public List<BaseObject> GetFileList()
{
    return ViewModel.ImagePickerVm.AttachmentList;
}

额外优化建议

  • 将AttachmentList改为ObservableCollection<BaseObject>,这样当集合内容变化时,邮件页面能自动感知到更新
  • 确保OnPropertyChanged方法正确实现,当前代码中Path属性调用的OnPropertyChanged未定义,可直接使用RaisedOnPropertyChanged方法替代

内容的提问来源于stack exchange,提问作者Lisa Maria

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 01:46:33