I've developed an UWP app for managing forms. Each form can contain a lot of images. These images raise memory leaks, and I don't found any solution to fix this.
Currently all the datas are stored in a SQLite database, including the images, as byte[].
The root object is a "Car_Form", which contains the "Images" in an ObservableCollection:
public class Car_Forms : BasePoco
{
// form_id
private int _form_id;
[PrimaryKey, NotNull, AutoIncrement]
public int form_id
{
get
{ return _form_id; }
set
{
if (value != _form_id)
{
_form_id = value;
RaisePropertyChanged(() => form_id);
}
}
}
//...
// images
private ObservableCollection<Images> _images;
[Ignore]
public ObservableCollection<Images> images
{
get
{ return _images; }
set
{
if (value != _images)
{
_images = value;
RaisePropertyChanged(() => images);
}
}
}
}
The "Images" object contains the reference to the "Car_Form", the byte[] that is stored in the SQLite database in the "image1" field, and the BitmapImage that is used for the display, in the "image_display" field:
public class Images : BasePoco
{
// image_id
private int _image_id;
[PrimaryKey, NotNull, AutoIncrement]
public int image_id
{
get
{ return _image_id; }
set
{
if (value != _image_id)
{
_image_id = value;
RaisePropertyChanged(() => image_id);
}
}
}
//...
// image1
private byte[] _image1;
[NotNull]
public byte[] image1
{
get
{ return _image1; }
set
{
if (value != _image1)
{
_image1 = value;
RaisePropertyChanged(() => image1);
}
}
}
// form_id
private int? _form_id;
public int? form_id
{
get
{ return _form_id; }
set
{
if (value != _form_id)
{
_form_id = value;
RaisePropertyChanged(() => form_id);
}
}
}
// bitmap_image
private BitmapImage _bitmap_image;
[Ignore]
public BitmapImage bitmap_image
{
get
{ return _bitmap_image; }
set
{
if (value != _bitmap_image)
{
_bitmap_image = value;
RaisePropertyChanged(() => bitmap_image);
}
}
}
}
In my XAML page, the "Images" are in displayed in a GridView like this:
<GridView ItemsSource="{x:Bind ViewModel.CarForm.images, Mode=OneWay}"
IsItemClickEnabled="True"
SelectionMode="Single"
Grid.Row="1">
<GridView.ItemTemplate>
<DataTemplate x:DataType="models:Images">
<Border BorderBrush="Gray" BorderThickness="2"
Background="White"
Padding="10"
Height="160" Width="225">
<Image Stretch="UniformToFill"
Source="{x:Bind image1, Mode=OneWay, Converter={StaticResource ByteArrayToBitmapImageConverter}}" />
</Border>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The datas are loaded from the SQLite database in the ViewModel:
private Car_Forms _carForm;
public Car_Forms CarForm
{
get { return _carForm; }
//set { Set(ref _carForm, value); }
set
{
this._carForm = value;
RaisePropertyChanged(() => CarForm);
}
}
private void LoadForm(Guid id)
{
CarForm = RepositoryService.GetById<Car_Forms>(id);
var formImages = RepositoryService.Where<Images>(im => im.IsDeleted == false && im.form_id == CarForm.form_id);
CarForm.images = new ObservableCollection<Images>(formImages);
//...
}
Then, the the BitmapImage is loaded through the "ByteArrayToBitmapImageConverter":
public class ByteArrayToBitmapImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
try
{
var bImg = (byte[])value;
if (bImg != null)
{
BitmapImage biImg = ByteArrayBitmapHelper.AsBitmapImage(bImg);
return biImg;
}
else
{
return null;
}
}
catch (Exception e)
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return null;
}
}
public static BitmapImage AsBitmapImage(this byte[] byteArray)
{
if (byteArray != null)
{
using (var stream = new InMemoryRandomAccessStream())
{
stream.WriteAsync(byteArray.AsBuffer()).GetResults();
var image = new BitmapImage();
stream.Seek(0);
image.SetSource(stream);
return image;
}
}
return null;
}
I have also implemented a "Cleanup()" method on the ViewModel, where I "clean" all the objects that are used:
public override void Cleanup()
{
//...
CarForm.images.Clear();
CarForm = null;
base.Cleanup();
}
But when I launch the app, I can see that all resources are not released: each time that I open the same form and that I come back to the list, there are 30 Mo that are not released.
laucnh of the app: "Home" page
display of the form
back to the "Home" page
display of the form
back to the "Home" page
display of the form
display of the form
back to the "Home" page
display of the form
=> Would you have any explanation? How could I optimize it?
This is happening because pages in UWP are not cached by default, so everytime you navigate to page, new instance of that page is created. You can set the NavigationCacheMode property of the page to NavigationCacheMode.Required so the pages will be cached and navigation will not be causing memory leaks.
Related
I've been trying to make a Custom search field that, on the fly, should add objects to a list when typing.
But for some reason it only shows the list with an item when i hot reload.
CreateHerdPageViewModel
public class CreateHerdPageViewModel : ViewModelBase
{
private IHerdService herdService;
private string searchInput;
public string SearchInput { get => searchInput;
set {
SetProperty(ref searchInput, value);
RaisePropertyChanged(nameof(HerdSearchResults));
}
}
private List<Herd> herdSearchResults;
public List<Herd> HerdSearchResults
{
get => herdSearchResults;
set {
SetProperty(ref herdSearchResults, value);
}
}
private List<Herd> allHerds;
public List<Herd> AllHerds { get => allHerds; set => SetProperty(ref allHerds, value); }
public DelegateCommand SearchChrOrAddressCommand { get; set; }
public CreateHerdPageViewModel(INavigationService navigationService, IHerdService herdService)
: base(navigationService)
{
this.herdService = herdService;
SearchChrOrAddressCommand = new DelegateCommand(SearchChrOrAddress);
}
private void SearchChrOrAddress()
{
Herd herdMatch = new Herd();
for (int i = 0; i < AllHerds.Count; i++)
{
herdMatch = AllHerds[i];
}
if (herdMatch.ChrAddress.Area.Contains(SearchInput))
{
if (HerdSearchResults.Contains(herdMatch) == false)
{
HerdSearchResults.Add(herdMatch);
RaisePropertyChanged(nameof(HerdSearchResults));
}
}
}
public async override void OnNavigatedTo(INavigationParameters parameters)
{
base.OnNavigatedTo(parameters);
AllHerds = await herdService.GetHerds();
HerdSearchResults = new List<Herd>();
}
}
}
CreateHerdPage.Xaml
xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
xmlns:b="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
xmlns:CustomRenderer="clr-namespace:ChrApp.CustomRenderer">
<yummy:PancakeView
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="3"
CornerRadius="10">
<CustomRenderer:NoUnderlineEntry
x:Name="SearchField"
Style="{StaticResource UpdateEntry}"
Margin="0"
TextChanged="RemovceSearchIcon"
Text="{Binding SearchInput}">
<CustomRenderer:NoUnderlineEntry.Behaviors>
<b:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding SearchChrOrAddressCommand}"/>
</CustomRenderer:NoUnderlineEntry.Behaviors>
</CustomRenderer:NoUnderlineEntry>
</yummy:PancakeView>
As you can see, i've tried different approaches to make it recognize changes, but without luck.
Can someone enlighten me on what i'm missing?
Thanks in advance
So the issue was that I used an ordinary List, and not an ObservableCollection. So swithcing to this solved the issue.
I have an employee table. and location field in employee table.
I have to use combobox to filter that. If I choose "A location" in combobox only A location people should come in screen if I choose B location only B location people should come in screen.
It's my xaml Entries and ComboBox.ParticularEntries is my all entries (A and B locations together)
Initialized ParticularEntries like that:
private IEnumerable<EntryReportParticular> _particularEntries;
public IEnumerable<EntryReportParticular> ParticularEntries
{
get { return _particularEntries; }
set { Set(ref _particularEntries, value); }
}
And EntryReportParticular Model Class:
public class EntryReportParticular : BindableItem
{
private Employee _employee;
public Employee Employee
{
get { return _employee; }
set { Set(ref _employee, value); }
}
private DateTime _entry;
public DateTime Entry
{
get { return _entry; }
set { Set(ref _entry, value, () => OnPropertyChanged(nameof(Duration))); }
}
private DateTime _exit;
public DateTime Exit
{
get { return _exit; }
set { Set(ref _exit, value, () => OnPropertyChanged(nameof(Duration))); }
}
public TimeSpan Duration { get { return Exit - Entry; } }
private Region _region;
public Region Region
{
get { return _region; }
set { Set(ref _region, value); }
}
}
It's my xaml ParticularEntries
<DataGrid
ItemsSource="{Binding ParticularEntries}"
AutoGenerateColumns="False"
IsReadOnly="True"
RowHeaderWidth="0"
GridLinesVisibility="All"
HorizontalGridLinesBrush="WhiteSmoke"
VerticalGridLinesBrush="WhiteSmoke"
Margin="4">
And It's my combobox with command.
<ComboBox
ItemsSource="{Binding Locations}"
SelectedItem ="{Binding SelectedLocation}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding LocationFilterCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
And It's my related part of ViewModel:
ComboBox:
private string _selectedLocation;
public string SelectedLocation
{
get { return _selectedLocation; }
set
{
_selectedLocation = value;
OnPropertyChanged("SelectedLocation");
Trace.WriteLine(SelectedLocation);
}
}
private ObservableCollection<string> _locations;
public ObservableCollection<string> Locations
{
get { return _locations; }
set
{
_locations = value;
OnPropertyChanged("Locations");
}
}
public EntryReportViewModel()//Constructor
{
Locations = new ObservableCollection<string>()
{
"A Location","B Location"
};
}
LocationFilterCommand(to filtered according to location without button)
#region LocationFilterCommand
private DelegateCommand _locationFilterCommand;
public DelegateCommand LocationFilterCommand
{
get { return _locationFilterCommand ?? (_locationFilterCommand = new DelegateCommand(CanLocationFilter, LocationFilter)); }
}
private bool CanLocationFilter()
{
if (ParticularEntries == null || DailyEntries == null || MonthlyEntries == null)
return false;
return true;
}
private void LocationFilter()
{
ParticularEntries.Select(pg => pg.Region.Location == _selectedLocation);
MonthlyEntries.Select(pg => pg.Employee.CostCenter.Location == _selectedLocation);
}
#endregion
I did that. I have ComboBox with A and B locations but when I choose A or B location anything changed.How can I fix this and how can I filtered according to location? What should I change in UI or others to do that?
Your code in LocationFilter make no sense at all.
ParticularEntries.Select(pg => pg.Region.Location == _selectedLocation);
It returns an IEnumerable<bool> but it is never assigned.
If you want to filter, you have to use Where.
But even if you change your code to
ParticularEntries = ParticularEntries.Where(pg => pg.Region.Location == _selectedLocation);
you will see a change, but you will face the next problem next time when you select a different location.
Solution
You need a collection with all unfiltered items stored inside a private field and use that for filtering.
private IEnumerable<EntryReportParticular> _allEntries;
private IEnumerable<EntryReportParticular> _particularEntries;
public IEnumerable<EntryReportParticular> ParticularEntries
{
get { return _particularEntries; }
set { Set(ref _particularEntries, value); }
}
private void LocationFilter()
{
ParticularEntries = _allEntries
.Where(pg => pg.Region.Location == _selectedLocation)
.ToList();
}
I added the OnActivated() into app.xaml.cs it is work correctly:
protected async override void OnActivated(IActivatedEventArgs args)
{
var continuationEventArgs = args as IContinuationActivatedEventArgs;
if (continuationEventArgs != null)
{
switch (continuationEventArgs.Kind)
{
case ActivationKind.PickFileContinuation:
FileOpenPickerContinuationEventArgs arguments = continuationEventArgs as FileOpenPickerContinuationEventArgs;
string passedData = (string)arguments.ContinuationData["keyParameter"];
StorageFile file = arguments.Files.FirstOrDefault(); // your picked file
addNewPlaceViewModel.OnFilesPicked(file);
// do what you want
break;
}
}
}
I hooked already FileOpenPicker into MVVM project correctly. This is my code:
private static readonly IEnumerable<string> SupportedImageFileTypes = new List<string> { ".jpeg", ".jpg", ".png" };
public AddNewPlaceViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
}
private async void OnFilesPicked(IStorageFile file)
{
if (file != null)
{
var bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(await file.OpenReadAsync());
Picture = bitmapImage;
//IN debugger in picture I have sht but in xaml i cannot show this.
}
}
}
private static void TriggerPicker(IEnumerable<string> fileTypeFilers, bool shouldPickMultiple = false)
{
var fop = new FileOpenPicker();
foreach (var fileType in fileTypeFilers)
{
fop.FileTypeFilter.Add(fileType);
}
if (shouldPickMultiple)
{
fop.PickMultipleFilesAndContinue();
}
else
{
fop.PickSingleFileAndContinue();
}
}
This is situation after Picture = bitmapImage;
I have also set up Binding and ICommand:
public ICommand UpdatePictureCommand
{
get { return new RelayCommand(o => TriggerPicker(SupportedImageFileTypes)); }
}
private ImageSource _Picture;
public ImageSource Picture
{
get
{
return _Picture;
}
set
{
_Picture = value;
OnPropertyChanged("Picture");
}
}
And this is my XAML in pivot item(button and Image) when I want to show photo which I have taken.
<Button Grid.Row ="4"
Content="Dodaj zdjęcie"
HorizontalAlignment="Center"
Command="{Binding UpdatePictureCommand}"/>
<Image Grid.Row="6"
Width="192"
Height="192"
Source="{Binding Picture, Mode=TwoWay}"
/>
A file open picker is working correctly(I can choose or take a photo) but after that I cannot see choosed/taked photo in my XAML. What is going wrong with that code?
you can create a converter something like this
[ValueConversion(typeof(Image), typeof(System.Windows.Media.ImageSource))]
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return null;
}
var bitmap = (Bitmap)value;
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bitmap.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
As is well known, CM doesn't support passing a object of complex type through NavigationService like MVVM Light. So I searched for a workaround and did it like this.
There are two viewmodels: MainPageViewModel and SubPageViewModel.
I first defined 3 classes, namely GlobalData, SnapshotCache and StockSnapshot. StockSnapshot is the type of which the object I want to pass between the 2 viewmodels.
public class SnapshotCache : Dictionary<string, StockSnapshot>
{
public StockSnapshot GetFromCache(string key)
{
if (ContainsKey(key))
return this[key];
return null;
}
}
public class GlobalData
{
private GlobalData()
{
}
private static GlobalData _current;
public static GlobalData Current
{
get
{
if (_current == null)
_current = new GlobalData();
return _current;
}
set { _current = value; }
}
private SnapshotCache _cachedStops;
public SnapshotCache Snapshots
{
get
{
if (_cachedStops == null)
_cachedStops = new SnapshotCache();
return _cachedStops;
}
}
}
public class StockSnapshot
{
public string Symbol { get; set; }
public string Message { get; set; }
}
Next, I call the navigation service on MainPageViewModel like this:
StockSnapshot snap = new StockSnapshot {Symbol="1", Message = "The SampleText is here again!" };
GlobalData.Current.Snapshots[snap.Symbol] = snap;
NavigationService.UriFor<SubPageViewModel>().WithParam(p=>p.Symbol,snap.Symbol).Navigate();
And on SubPageViewModel I've got this:
private string _symbol;
public string Symbol
{
get { return _symbol; }
set
{
_symbol = value;
NotifyOfPropertyChange(() => Symbol);
}
}
public StockSnapshot Snapshot
{
get { return GlobalData.Current.Snapshots[Symbol]; }
}
And that's where the problem lies. When I run the program, I find out that it always runs to the getter of Snapshot first, when Symbol hasn't been initialized yet. So later I've tried adding some extra code to eliminate the ArgumentNullException so that it can run to the setter of Symbol and then everything goes fine except that the UI doesn't get updated anyway.
Could anyone tell me where I've got wrong?
Thx in advance!!
Why not just use:
private string _symbol;
public string Symbol
{
get { return _symbol;}
set
{
_symbol = value;
NotifyOfPropertyChange(() => Symbol);
NotifyOfPropertyChange(() => Snapshot);
}
}
public StockSnapshot Snapshot
{
get { return Symbol!=null? GlobalData.Current.Snapshots[Symbol]:null; }
}
In this case you don't try and get the data from GlobalData when Symbol is null (sensible approach anyway!) and when "Symbol" is set you call NotifyOfPropertyChange() on Snapshot to force a re-get of the property.
So I have a property grid that I want to have an object bound to. When the application is running a button will show up and display a form which will allow that object to have its properties set and returned back to the property that called the form.
Here is what I have so far:
I cant get the property to show up in my property grid. Basically I want to use a form to fill in other items in the property grid.
I hope my question was clear enough...
public class OptoSetupFormEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
IWindowsFormsEditorService svc = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
DataTemp opto = value as DataTemp;
if (svc != null && opto != null)
{
using (OptoSigmaSetup form = new OptoSigmaSetup())
{
if (svc.ShowDialog(form) == DialogResult.OK)
{
opto.Direction = form.Direction;
opto.FunctionToCall = form.FunctionToCall;
opto.Duration = form.Duration;
// OptoSigmaTest.Command = form.
}
}
}
return opto; // can also replace the wrapper object here
}
}
[Editor(typeof(OptoSetupFormEditor),typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
public DataTemp Test1
{
set
{
this.Duration = value.Duration ;
this.Direction = value.Direction;
this.FunctionUsed = value.FunctionToCall;
}
}
[ReadOnly(true), Category("Setup")]
public string FunctionUsed
{
get { return functionUsed; }
set { functionUsed = value; }
}
[ReadOnly(true), Category("Setup")]
public int Duration
{
get { return duration; }
set { duration = value; }
}
[ReadOnly(true),Category("Setup")]
public string Direction
{
get { return direction; }
set { direction = value; }
}
public class DataTemp
{
private int duration = 0;
private string direction = "Positive";
private string functionToCall = "Home";
public string FunctionToCall
{
get { return functionToCall; }
set { functionToCall = value; }
}
public int Duration
{
get { return duration; }
set { duration = value; }
}
public string Direction
{
get { return direction; }
set { direction = value; }
}
}
Thanks in advance. If it needs more clarification, please let me know
Have you tried augmenting the properties you wish to display with the Browsable attribute?
[Browsable(true)]
[ReadOnly(true), Category("Setup")]
public string FunctionUsed
{
get { return functionUsed; }
set { functionUsed = value; }
}
MSDN: BrowsableAttribute
EDIT:
Hm, according to MSDN, properties without this attribute, or those which have the attribute specified with a value of true, should display in Property windows; so this is a suggestion left wanting.