Xamarin在ListView中绑定Image为二进制数组。从可观察集合中删除条目时的问题

问题描述 投票:0回答:1

我有一个xamarin android应用程序,它提供了制作图像的选项。这些图像将被存储到一个数据库中。现有的图像将显示在一个列表视图中(作为预览)。您需要能够删除这些图像。除了图像的 "删除 "之外,一切都很好.我通过ObservableCollection来绑定图像。每个对象都有一个名为 "ImageDocumentData "的二进制数组属性。这个属性将被用作 "Image.Source "的绑定。我正在使用一个转换器来绑定,以便将二进制数组转换为一个ImageSource(通过MemoryStream)。然而,如果我在预览列表视图中有多个图像,并且我开始从ObservableCollection中移除图像,我就会得到运行时错误。

System.ObjectDisposedException: 'Cannot access a closed Stream.'。

当在运行时重新安排listview ui时,他又没有进入转换器函数。我怀疑如下:每次在ObservableCollection中添加一个新的图像后,图像控件都会处置ImageSource流。当我从ObservableCollection中删除一个条目后,他正在取内部缓存的ImageSource对象,并试图再次从他已经处置过的同一个流中读取。他需要再次获得图像数据,因为在我从集合中删除一个条目后,他需要重新排列(重新绘制)listview.我真的不知道如何解决这个问题。我需要在图像源中使用一个流,因为我只有一个二进制数组(没有文件,没有可能的uri).这只是我的假设。然而我看不到任何其他合理的解释来解释这个运行时错误。我已经尝试了一些其他选项,但到目前为止没有一个对我有用,所以我希望这里的人可以给我一个正确的方向的提示。

这是我的DTO。

    [DataContract]
    public class RequestItemImageDocument : INotifyPropertyChanged
    {

        #region Fields
        private Guid _requestItemImageDocId;
        private Guid _orderItemId;
        private byte[] _imagedocData;
        private string _documentDescription;
        private DateTime? _storageDate;
        private byte _isSynced;
        #endregion

        #region Properties
        [DataMember]
        public Guid RequestItemImageDocumentId 
        {
            get 
            {
                return _requestItemImageDocId;
            }
            set 
            {
                _requestItemImageDocId = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("RequestItemImageDocumentId"));
            } 
        }
        [DataMember]
        public Guid OrderItemId 
        {
            get
            {
                return _orderItemId;
            }
            set
            {
                _orderItemId = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("OrderItemId"));
            }
        }

        [DataMember]
        public byte[] ImageDocumentData 
        {
            get
            {
                return _imagedocData;
            }
            set
            {
                _imagedocData = value;
                if (PropertyChanged != null) 
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("ImageDocumentData"));

                }
            }
        }
        [DataMember]
        public string DocumentDescription 
        {
            get
            {
                return _documentDescription;
            }
            set
            {
                _documentDescription = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("DocumentDescription"));
            }
        }
        [DataMember]
        public DateTime? StorageDate 
        {
            get
            {
                return _storageDate;
            }
            set
            {
                _storageDate = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("StorageDate"));
            }
        }
        [DataMember]
        public byte IsSynced 
        {
            get
            {
                return _isSynced;
            }
            set
            {
                _isSynced = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("IsSynced"));
            }
        }
        #endregion
        #region Constr
        public RequestItemImageDocument() 
        {

        }
        #endregion
        public event PropertyChangedEventHandler PropertyChanged;
    }

这是我的视图

<?xml version="1.0" encoding="utf-8" ?>
<ui:PasAMBasePage
    x:Class="AssetManagement.Mobile.Core.UI.OrderItemImageDocumentationPage"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:actionMenu="clr-namespace:Client.ApplicationDataModel.UI.Controls.ActionMenu;assembly=Client.ApplicationDataModel.UI"
    xmlns:controls="clr-namespace:AssetManagement.Mobile.Core.Controls"
    xmlns:controls1="clr-namespace:UI.XF.Controls;assembly=UI"
    xmlns:AppList="clr-namespace:Client.ApplicationDataModel.UI.Controls.AppList;assembly=Client.ApplicationDataModel.UI"
    xmlns:res="clr-namespace:AssetManagement.Mobile.Core.Resources"
    xmlns:ui="clr-namespace:AssetManagement.Mobile.Core.UI"
    xmlns:valueconverter="clr-namespace:AssetManagement.Mobile.Core.Classes.ValueConverter"
    xmlns:viewmodel="clr-namespace:AssetManagement.Mobile.Core.ViewModels"
    Title="Bild Dokumentation" 
    x:TypeArguments="viewmodel:OrderItemImageDocuViewModel"><!--{res:Translate OrderItemImageDocumentary}-->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <StackLayout Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
            <ListView ItemsSource="{Binding OrderItemImages}"
          HasUnevenRows="true"
          ItemSelected="OnListViewItemSelected"
          ItemTapped="OnListViewItemTapped" x:Name="ImagePreviewListView">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid Padding="10">
                                <Grid.Resources>
                                    <valueconverter:ByteArrayToImageSourceConverter x:Key="SourceConverter" />
                                </Grid.Resources>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto" />
                                    <RowDefinition Height="*" />
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                           <Image Grid.RowSpan="2"
                           Source="{Binding ImageDocumentData, Converter={StaticResource SourceConverter}}"
                           Aspect="AspectFit"
                           HeightRequest="120"
                           WidthRequest="120" />
                                <Label Grid.Column="1"
                           Text="{Binding DocumentDescription}"
                           FontAttributes="Bold" />
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
        <StackLayout Grid.Row="1" Grid.ColumnSpan="3" Grid.Column="0" Orientation="Horizontal" HorizontalOptions="End" VerticalOptions="End">
            <controls:ImageButtonControl
                Clicked="btnRemove_Clicked"
                FontSize="30"
                Glyph="trash-alt"
                HeightRequest="60"
                HorizontalOptions="End"/>
            <controls:ImageButtonControl
                Clicked="btnAdd_Clicked"
                FontSize="30"
                Glyph="plus"
                HeightRequest="60"
                HorizontalOptions="End"/>
            <controls:ImageButtonControl
                Clicked="btnSave_Clicked"
                FontSize="30"
                Glyph="save"
                HeightRequest="60"
                HorizontalOptions="End"/>

        </StackLayout>
    </Grid>
</ui:PasAMBasePage>

这是我的视图背后的代码

    public partial class OrderItemImageDocumentationPage : PasAMBasePage<OrderItemImageDocuViewModel>
    {
        private IAppConfiguration _appConfig;

        public OrderItemImageDocumentationPage()
        {
            InitializeComponent();
        }

        public OrderItemImageDocumentationPage(OrderItemImageDocuViewModel viewModel, IEventLogService eventLog, ILifetimeScope scope,
            IUserInterfaceService uiService, IAppConfiguration appConfig)
            : base(viewModel, eventLog, scope, uiService)
        {
            InitializeComponent();

            _appConfig = appConfig;

            ViewModel.PropertyChanged += ViewModel_PropertyChanged;
        }

        private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(OrderItemListViewModel.Order))
            {
                //orderItemAppList.EventContext = ViewModel;
            }
        }

        private void OnListViewItemTapped(object sender, Xamarin.Forms.ItemTappedEventArgs e)
        {

        }

        private void OnListViewItemSelected(object sender, Xamarin.Forms.SelectedItemChangedEventArgs e)
        {

        }


        private async void btnAdd_Clicked(object sender, EventArgs e)
        {
            RequestItemImageDocument document = new RequestItemImageDocument();
            document.RequestItemImageDocumentId = Guid.NewGuid();
            await CrossMedia.Current.Initialize();
            if (!CrossMedia.Current.IsCameraAvailable ||
    !CrossMedia.Current.IsTakePhotoSupported)
            {
                await DisplayAlert("No Camera", "No camera available.", "OK");
                return;
            }
            var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
            {
                Directory = "AssetDocumentary",
                SaveToAlbum = true,
                CompressionQuality = 75,
                CustomPhotoSize = 50,
                PhotoSize = PhotoSize.MaxWidthHeight,
                MaxWidthHeight = 2000,
                DefaultCamera = CameraDevice.Front
            });
            if (!string.IsNullOrEmpty(file.Path)) 
            {
                var result = await UserDialogs.Instance.PromptAsync("Fügen Sie eine optionale Textbeschreibung hinzu.", "Beschreibung");
                document.DocumentDescription = result.Text;
                byte[] data = null;
                using (var stream = file.GetStream()) 
                {
                    var fInfo = new System.IO.FileInfo(file.Path);
                    data = new byte[fInfo.Length];
                    stream.Read(data, 0, data.Length);
                }
                document.ImageDocumentData = data;
                base.ViewModel.OrderItemImages.Add(document);

                Android.Content.Context context = Android.App.Application.Context;
                Android.Net.Uri filesUri = Android.Provider.MediaStore.Files.GetContentUri("external");
                string where = Android.Provider.MediaStore.MediaColumns.Data + "=?";
                //System.IO.File.Delete(file.Path);
                context.ContentResolver.Delete(filesUri, where, new string[] { file.Path });
            }

        }

        private void btnSave_Clicked(object sender, EventArgs e)
        {

        }

        private void btnRemove_Clicked(object sender, EventArgs e)
        {
            if (ImagePreviewListView.SelectedItem != null) 
            {
                var selectedItem = ImagePreviewListView.SelectedItem as RequestItemImageDocument;
                var test = ViewModel.OrderItemImages.FirstOrDefault(
                    c => c.RequestItemImageDocumentId.Equals(((RequestItemImageDocument)ImagePreviewListView.SelectedItem)
                    .RequestItemImageDocumentId));
                ImagePreviewListView.SelectedItem = null;
                ViewModel.OrderItemImages.Remove(test);
            }
        }
    }

这是我的转换器

    public class ByteArrayToImageSourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            ImageSource xValue = null ;
            try 
            {
                if (value != null) 
                {
                    MemoryStream mem = new MemoryStream((byte[])value);
                    xValue = ImageSource.FromStream(new Func<Stream>(() => { return mem; }));
                }

            }catch(Exception ex) 
            {

            }
            return xValue;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }

这是我的视图模型

    public class OrderItemImageDocuViewModel : ApplicationDataBaseViewModel, INavigationEventHandler
    {
        #region Klassenvariablen

        private readonly int _pagingSize = 20;
        private IAppConfiguration _appConfig;
        private ILifetimeScope _scope;
        private IBarcodeParserService _barcodeParser;
        private IEventLogService _eventLog;
        private IUserDataService _userData;
        private int _itemsToLoadCount;
        private ObservableCollection<RequestItemImageDocument> _orderItemImages = new ObservableCollection<RequestItemImageDocument>();
        private OrderItem _orderItemContext = null;
        #endregion Klassenvariablen

        #region Konstruktor

        public OrderItemImageDocuViewModel(ILifetimeScope scope, IDataRepositoryService dataRepository, IBarcodeParserService barcodeParserService, IEventLogService eventLog, IUserDataService userData, ITranslationService translationService, IAppConfiguration appConfig)
            : base(dataRepository)
        {
            _eventLog = eventLog.Initialize(typeof(OrderItemImageDocuViewModel));
            _scope = scope;
            _appConfig = appConfig;
            _userData = userData;
            _barcodeParser = barcodeParserService;
            TranslationService = translationService;

            _itemsToLoadCount = _pagingSize;


        }

        #endregion Konstruktor

        #region Properties

        [NavigationParameter]
        public Order Order { get; set; }

        public IEventLogService EventLog => _eventLog;

        public bool IsLoading { get; set; }

        public bool DatabasesMissing { get; set; }



        public ITranslationService TranslationService { get; set; }

        public IList<OrderItem> OrderItems { get; set; }

        public IOrderTypeScanHandling ScanHandler { get; set; }

        public ObservableCollection<RequestItemImageDocument> OrderItemImages 
        {
            get 
            {

                return _orderItemImages;
            }
            set 
            {
                _orderItemImages = value;
                base.OnPropertyChanged("OrderItemImages");
            }
        }
        #endregion Properties

        #region Functions

        #endregion

        public override async void OnNavigationParametersProvided(NavigationContext context)
        {
            if (!DatabasesMissing)
            {
                IsLoading = true;

                try
                {
                    //await InitializeData();

                    //await LoadDataOnDemand();
                }
                catch (Exception ex)
                {
                    //ex.TrackError();
                }

                IsLoading = false;
            }
        }

    }

[更新]我已经提取了具体的代码,并将其放入一个独立的解决方案中。https:/github.comQ-Tec90ImageListTest 为了重现这个问题,只需在列表视图中添加3张图片。之后选择列表视图中的第二个条目,然后点击删除按钮。你会得到运行时异常。我是在Android 6设备上运行的。设备或模拟器需要有一个前置摄像头,以便制作图像。

c# image listview data-binding xamarin.android
1个回答
0
投票

我发现了这个问题。它位于转换器中。

这是我以前的转换器功能。需要在委托函数中定义内存流。否则,当列表视图重新渲染控件时,它将尝试从旧的已经处置的内存流中读取。

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            ImageSource xValue = null ;
            try 
            {
                if (value != null) 
                {
                    MemoryStream mem = new MemoryStream((byte[])value);
                    xValue = ImageSource.FromStream(new Func<Stream>(() => { return mem; }));
                }

            }catch(Exception ex) 
            {

            }
            return xValue;
        }

修正版。

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            ImageSource xValue = null ;
            try 
            {
                if (value != null) 
                {

                    xValue = ImageSource.FromStream(
                        new Func<Stream>(() => 
                        {
                            MemoryStream mem = new MemoryStream((byte[])value);
                            return mem; 
                        }));
                }

            }catch(Exception ex) 
            {

            }
            return xValue;
        }
© www.soinside.com 2019 - 2024. All rights reserved.