我有一个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设备上运行的。设备或模拟器需要有一个前置摄像头,以便制作图像。
我发现了这个问题。它位于转换器中。
这是我以前的转换器功能。需要在委托函数中定义内存流。否则,当列表视图重新渲染控件时,它将尝试从旧的已经处置的内存流中读取。
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;
}