Create table in SQLite with generic class 'Quantity' as a picker - ERROR not a valid type for SQLite DB - sqlite

Morning All,
xamarin forms - populate picker in MVVM from SQLite DB issue
So...Ive used the following post
https://www.c-sharpcorner.com/article/populate-picker-using-mvvm/
to help me include a populated picker using MVVM in my project, using a picker to allowing the user to select a 'Quantity' to order for each product (which all works fine as its populated from code, but now refactoring to load from SQLite..please see current code below)..
//ProductModel
public class ProductModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _ProductId;
private string _BrandName;
public ObservableCollection<Quantity> _ListQuantites;
private Quantity _selectedQuantity;
//ETC have removed the other properties for sake of this Q
//Constructor
public ProductModel()
{
//Subscription
this.PropertyChanged += OnPropertyChanged;
}
[PrimaryKey, AutoIncrement]
public int ProductId
{
get { return _ProductId; }
set
{
if (_ProductId == value) return;
_ProductId = value;
OnPropertyChanged();
}
}
public ObservableCollection<Quantity> ListQuantites
{
get
{
return _ListQuantites;
}
set
{
_ListQuantites = value;
OnPropertyChanged();
}
}
public Quantity SelectedQuantity
{
get
{
return _selectedQuantity;
}
set
{
if (value == null)
{
_selectedQuantity = _selectedQuantity;
}
else
{
_selectedQuantity = value;
OnPropertyChanged();
}
}
}
//OnPropertyChanged
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SelectedQuantity))
{
//test quantity amount
}
}
// [NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Up Until now I was populating the ProductModel with its picker, from the constructor of the ProductViewModel page with:
WineList = new ObservableCollection<ProductModel>();
WineList.Add(new ProductModel { ProductId = 1, BrandName = "Mc guigans", Grape = "Red", ListQuantites = List_Quantites, Image = "W.png", Description = "Fruity Flav", Size="700ml", Price = 10.00M, SubTotalForItem = 0.00M, Genre = "Wine" });
//etc...and so on
//then
List_Quantites = PickerService.GetQuantitiesForProductPage();
//Picker service
public static ObservableCollection<QuantityModel> GetQuantitiesForProductPage()
{
var quantities = new ObservableCollection<QuantityModel>()
{
new QuantityModel() {Key=1, Value="0"},
new QuantityModel() {Key=2, Value="1"},
new QuantityModel() {Key=3, Value="2"},
new QuantityModel() {Key=4, Value="3"}
//etc
};
return quantities;
}
//XAML
<Picker Grid.Column="3" Grid.Row="0" Title=" " VerticalOptions="Center" x:Name="productPicker" VerticalTextAlignment="Center" HorizontalOptions="EndAndExpand" ItemsSource="{Binding ListQuantites}" ItemDisplayBinding="{Binding Value}" SelectedIndexChanged="QuantityChanged" SelectedItem ="{Binding SelectedQuantity}"/>
So...yeah as I said all works fine and dandy...But now I would like to load the ProductModel list from a table in SQLite....I have already used SQLite basic CRUD operations to create, load, view, update, edit...etc for orders made...so this is also working...the problem I seem to be having is creating the table, it is failing when I try to create an entry in the table with 'Quantity'....obviously...so I have changed the code...change 'Quantity' to object, then the plan being to populate this on the VM...but this also didnt work...
found another post relating to this:
https://forums.xamarin.com/discussion/2546/create-table-in-xamarin-throws-exception-because-of-generic-list
So 'Quantity' class is not a valid type for SQLite DB value...but turns out neither is object...has anyone idea for a work around for this...or some advice on how to refactor to resolve this?
any help or point in the right direction is appreciated thank Y

Related

Validating forms only on submit with Blazor

I've recently started using Blazor. Is there a way to trigger form model validation only on submit, instead of live on each change?
Just for clarification, let's say I have something like this:
<EditForm Model="this" OnValidSubmit="SubmitForm">
<DataAnnotationsValidator />
<ValidationSummary />
<Label For="Name">Name</Label>
<InputText id="Name" name="Name" class="form-control" #bind-Value="Name"/>
<button type="submit">Save</button>
</EditForm>
#code {
[StringLength(10, ErrorMessage="Name too long")]
public string Name { get; set; }
private async Task SubmitForm()
{
// ...
// send a POST request
}
}
By default, it seems like the validity of the field and the error messages displayed in the ValidationSummary get re-evaluated on every change of the text input (e.g. as soon as I delete the 11th character from the input, the "too long" message disappears).
I would prefer if the displayed messages would remain frozen until the Submit button is clicked.
I suppose it would be possible to implement it by removing the ValidationSummary component and implementing a custom solution (e.g. displaying a List of error messages that's refreshed only on submit), but I was wondering if there is some idiomatic solution that I'm not aware of.
When validation occurs is controlled by the Validator you're using.
There are two events that you can receive from EditContext:
OnValidationRequested is invoked either when EditContext.Validate is called or as part of the form submission process.
OnFieldChanged is invoked every time a field value is changed.
A validator uses these events to trigger it's validation process, and outputs the results to the EditContext's ValidationMessageStore.
DataAnnotationsValidator wires up for both events and triggers validation whenever either is invoked.
There are other validators out there, and writing your own is not too difficult. Other than those from the usual control suppliers, there's Blazored, or mine. Mine is documented here - https://shauncurtis.github.io/articles/Blazor-Form-Validation.html. it has a DoValidationOnFieldChange setting!
#enet's answer sparked an alternative answer. Build your own DataAnnotationsValidator.
Here's the EditContext Extensions code. It's a modified version of the original MS Code with some extra control arguments.
using Microsoft.AspNetCore.Components.Forms;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
namespace StackOverflowAnswers;
public static class EditContextCustomValidationExtensions
{
public static IDisposable EnableCustomValidation(this EditContext editContext, bool doFieldValidation, bool clearMessageStore)
=> new DataAnnotationsEventSubscriptions(editContext, doFieldValidation, clearMessageStore);
private static event Action? OnClearCache;
private static void ClearCache(Type[]? _)
=> OnClearCache?.Invoke();
private sealed class DataAnnotationsEventSubscriptions : IDisposable
{
private static readonly ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo?> _propertyInfoCache = new();
private readonly EditContext _editContext;
private readonly ValidationMessageStore _messages;
private bool _doFieldValidation;
private bool _clearMessageStore;
public DataAnnotationsEventSubscriptions(EditContext editContext, bool doFieldValidation, bool clearMessageStore)
{
_doFieldValidation = doFieldValidation;
_clearMessageStore = clearMessageStore;
_editContext = editContext ?? throw new ArgumentNullException(nameof(editContext));
_messages = new ValidationMessageStore(_editContext);
if (doFieldValidation)
_editContext.OnFieldChanged += OnFieldChanged;
_editContext.OnValidationRequested += OnValidationRequested;
if (MetadataUpdater.IsSupported)
{
OnClearCache += ClearCache;
}
}
private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs)
{
var fieldIdentifier = eventArgs.FieldIdentifier;
if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo))
{
var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
var validationContext = new ValidationContext(fieldIdentifier.Model)
{
MemberName = propertyInfo.Name
};
var results = new List<ValidationResult>();
Validator.TryValidateProperty(propertyValue, validationContext, results);
_messages.Clear(fieldIdentifier);
foreach (var result in CollectionsMarshal.AsSpan(results))
{
_messages.Add(fieldIdentifier, result.ErrorMessage!);
}
// We have to notify even if there were no messages before and are still no messages now,
// because the "state" that changed might be the completion of some async validation task
_editContext.NotifyValidationStateChanged();
}
}
private void OnValidationRequested(object? sender, ValidationRequestedEventArgs e)
{
var validationContext = new ValidationContext(_editContext.Model);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(_editContext.Model, validationContext, validationResults, true);
// Transfer results to the ValidationMessageStore
_messages.Clear();
foreach (var validationResult in validationResults)
{
if (validationResult == null)
{
continue;
}
var hasMemberNames = false;
foreach (var memberName in validationResult.MemberNames)
{
hasMemberNames = true;
_messages.Add(_editContext.Field(memberName), validationResult.ErrorMessage!);
}
if (!hasMemberNames)
{
_messages.Add(new FieldIdentifier(_editContext.Model, fieldName: string.Empty), validationResult.ErrorMessage!);
}
}
_editContext.NotifyValidationStateChanged();
}
public void Dispose()
{
if (_clearMessageStore)
_messages.Clear();
if (_doFieldValidation)
_editContext.OnFieldChanged -= OnFieldChanged;
_editContext.OnValidationRequested -= OnValidationRequested;
_editContext.NotifyValidationStateChanged();
if (MetadataUpdater.IsSupported)
{
OnClearCache -= ClearCache;
}
}
private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier, [NotNullWhen(true)] out PropertyInfo? propertyInfo)
{
var cacheKey = (ModelType: fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName);
if (!_propertyInfoCache.TryGetValue(cacheKey, out propertyInfo))
{
// DataAnnotations only validates public properties, so that's all we'll look for
// If we can't find it, cache 'null' so we don't have to try again next time
propertyInfo = cacheKey.ModelType.GetProperty(cacheKey.FieldName);
// No need to lock, because it doesn't matter if we write the same value twice
_propertyInfoCache[cacheKey] = propertyInfo;
}
return propertyInfo != null;
}
internal void ClearCache()
=> _propertyInfoCache.Clear();
}
}
And the CustomValidation component:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace StackOverflowAnswers;
public class CustomValidation : ComponentBase, IDisposable
{
private IDisposable? _subscriptions;
private EditContext? _originalEditContext;
[CascadingParameter] EditContext? CurrentEditContext { get; set; }
[Parameter] public bool DoEditValidation { get; set; } = false;
/// <inheritdoc />
protected override void OnInitialized()
{
if (CurrentEditContext == null)
{
throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. For example, you can use {nameof(DataAnnotationsValidator)} " +
$"inside an EditForm.");
}
_subscriptions = CurrentEditContext.EnableCustomValidation(DoEditValidation, true);
_originalEditContext = CurrentEditContext;
}
/// <inheritdoc />
protected override void OnParametersSet()
{
if (CurrentEditContext != _originalEditContext)
{
// While we could support this, there's no known use case presently. Since InputBase doesn't support it,
// it's more understandable to have the same restriction.
throw new InvalidOperationException($"{GetType()} does not support changing the " +
$"{nameof(EditContext)} dynamically.");
}
}
/// <inheritdoc/>
protected virtual void Dispose(bool disposing)
{
}
void IDisposable.Dispose()
{
_subscriptions?.Dispose();
_subscriptions = null;
Dispose(disposing: true);
}
}
You can use it like this:
<EditForm EditContext=this.editContext OnValidSubmit=OnValidSubmit>
<CustomValidation DoEditValidation=false/>
#*<DataAnnotationsValidator/>*#
<div class="row">
<div class="col-2">
Date:
</div>
<div class="col-10">
<InputDate #bind-Value=this.Record.Date></InputDate>
</div>
</div>
.......

Xaramin form -calling variable from other .cs file

I am doing a quiz game in Xaramin. forms. and for the score function. if the user got a correct answer, I want the score will add 1.but in my case even the give the correct answer, the score is not adding.
I am also trying to bind to the "score" variable to a label. I want to know if i put a correct code or not.
Button
private void submit_Clicked(object sender, EventArgs e)
{
string answer = this.answer.Text;
string canswer = "correct";
if (answer != null)
{
string ranswer = answer.Replace(" ", string.Empty);
if (ranswer.ToLower() == canswer)
{
DisplayAlert("GoodJob", "You got the correct answer", "OK");
bindingModel b = new bindingModel();
b.score++;
(sender as Button).IsEnabled = false;
}
else
{
DisplayAlert("Unfortunately", "Your answer is wrong", "OK");
(sender as Button).IsEnabled = false;
}
}
}
ViewModel
public class bindingModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int displayScore => Score;
public int score = 0;
void OnPropertyChanged(int score)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(score.ToString()));
}
public int Score
{
get => score;
set
{
if (score != value)
{
score = value;
OnPropertyChanged(score);
}
}
}
}
Model
<Label Text="{Binding Score}"/>
in your page constructor, keep a reference to your VM
bindingModel VM;
// this is your constructor, the name will match your page name
public MyPage()
{
InitializeComponent();
this.BindingContext = VM = new bindingModel();
...
}
then in your event handler, you do NOT need to create a new bindingModel
// update the Count on the VM
VM.Count++;
Answer
There's two things broken here:
You are re-initializing your ViewModel instead of referencing the same instance
You are passing the wrong value into PropertyChangedEventArgs
1. Referencing the View Model
You are re-initializing the ViewModel every time by calling bindingModel b = new bindingModel();
Lets initialize the ViewModel once, store it as a field, set it as the BindingContext for our ContentPage, and reference that field in submit_Clicked
public partial class QuizPage : ContentPage
{
readonly bindingModel _bindingModel;
public QuizPage()
{
_bindingModel = new bindingModel();
BindingContext = _bindingModel;
}
private async void submit_Clicked(object sender, EventArgs e)
{
string answer = this.answer.Text;
string canswer = "correct";
Button button = (Button)sender;
if (answer != null)
{
string ranswer = answer.Replace(" ", string.Empty);
if (ranswer.ToLower() == canswer)
{
await DisplayAlert("GoodJob", "You got the correct answer", "OK");
_bindingModel.score++;
button.IsEnabled = false;
}
else
{
await DisplayAlert("Unfortunately", "Your answer is wrong", "OK");
button.IsEnabled = false;
}
}
}
}
2. PropertyChangedEventArgs
You need to pass in the name of the property to PropertyChangedEventArgs.
They way PropertyChanged works is that it announces the name of the property that has changed. In this case, it needs to broadcast that the Score property has changed.
Let's use nameof(Score) to pass in the string "Score" to PropertyChangedEventArgs:
void OnScorePropertyChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(displayScore)));
}
public int Score
{
get => score;
set
{
if (score != value)
{
score = value;
OnScorePropertyChanged();
}
}
}

Strangeness with DataContext and GridView / ListView

I have a Windows 8 store app based off of the grouped template project, with some renames etc. However, I'm having a hard time getting the ItemsSource databinding to work for both non-snapped and snapped visual states.
I have a property, that, when set, changes the ItemsSource property, but I can only get one of the controls to bind at a time (either the GridView for non-snapped, or the ListView for snapped).
When I use the following, only the non-snapped binding works and the snapped binding shows no items:
protected PickLeafModel ListViewModel
{
get
{
return (PickLeafModel)m_itemGridView.ItemsSource;
}
set
{
m_itemGridView.ItemsSource = value;
m_snappedListView.ItemsSource = value;
}
}
If I comment out one of the setters, the snapped view shows items but the non-snapped view shows nothing:
protected PickLeafModel ListViewModel
{
get
{
return (PickLeafModel)m_itemGridView.ItemsSource;
}
set
{
//m_itemGridView.ItemsSource = value;
m_snappedListView.ItemsSource = value;
}
}
It's as if I can bind my view model only to one property at a time. What am I doing wrong?
Since I am generating my data model on another thread (yes, using the thread pool), I cannot make it inherit from DependencyObject. If I do, I get a WrongThreadException.
So to make it work I have done the following:
public class PickLeafModel : IEnumerable
{
public PickLeafModel()
{
}
public IEnumerator GetEnumerator()
{
if (m_enumerator == null)
{
m_enumerator = new PickLeafModelViewDataEnumerator(m_data, m_parentLeaf);
}
return m_enumerator;
}
private SerializableLinkedList<PickLeaf> m_data =
new SerializableLinkedList<PickLeaf>();
}
and then my items look like this:
// Augments pick leafs by returning them wrapped with PickLeafViewData.
class PickLeafModelViewDataEnumerator : IEnumerator
{
public PickLeafModelViewDataEnumerator(
SerializableLinkedList<PickLeaf> data, PickLeaf parentLeaf)
{
m_viewDataList =
new System.Collections.Generic.LinkedList<PickLeafViewData>();
foreach (PickLeaf leaf in data)
{
PickLeafViewData viewData = new PickLeafViewData();
viewData.copyFromPickLeaf(leaf, parentLeaf);
m_viewDataList.AddLast(viewData);
}
m_enumerator = m_viewDataList.GetEnumerator();
}
public void Dispose()
{
m_viewDataList = null;
m_enumerator = null;
}
public object Current
{
get
{
return m_enumerator.Current;
}
}
public bool MoveNext()
{
return m_enumerator.MoveNext();
}
public void Reset()
{
m_enumerator.Reset();
}
private IEnumerator<PickLeafViewData> m_enumerator = null;
private System.Collections.Generic.LinkedList<PickLeafViewData>
m_viewDataList;
}
}
Is there something I'm doing fundamentally wrong?
Help appreciated.
Thanks!
Thankfully there is a much easier way to do what you are trying!
Create a class called your ViewModel as shown below:
public class DataViewModel
{
public DataViewModel()
{
Data = new ObservableCollection<PickLeafViewData>(new PickLeafModelViewDataEnumerator(m_data, m_parentLeaf));
}
public ObservableCollection<PickLeafViewData> Data
{
get;
set;
}
}
Now on the code behind set the Page.DataConected to equal an instance of the above class.
And finally on both your snapped listview, and the grid view set the item source to this:-
ItemsSource="{Binding Data}"
That should work nicely for you.
Thanks to Ross for pointing me in the right direction.
I'm not 100% happy with this solution, but it does work. Basically the idea is that after I get back the PickLeafModel from the worker threads, I transplant its internal data into a derived version of the class which is data binding aware.
public class PickLeafViewModel : PickLeafModel, IEnumerable
{
public PickLeafViewModel()
{
}
public PickLeafViewModel(PickLeafModel model)
{
SetData(model);
}
public void SetData(PickLeafModel model)
{
model.swap(this);
}
public IEnumerator GetEnumerator()
{
if (m_observableData == null)
{
m_observableData = new ObservableCollection<PickLeafViewData>();
var data = getData();
PickLeaf parentLeaf = getParentLeaf();
foreach (PickLeaf leaf in data)
{
PickLeafViewData viewData = new PickLeafViewData();
viewData.copyFromPickLeaf(leaf, parentLeaf);
m_observableData.Add(viewData);
}
}
return m_observableData.GetEnumerator();
}
and the page code is as follows:
protected PickLeafViewModel ListViewModel
{
get
{
return DataContext as PickLeafViewModel;
}
set
{
DataContext = value;
}
}
whenever I want to set ListViewModel, I can do this:
ListViewModel = new PickLeafViewModel(model);
and swap looks like:
private static void swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
// Swaps internals with the other model.
public void swap(PickLeafModel other)
{
swap(ref m_data, ref other.m_data);
...
Also, PickLeafModelViewDataEnumerator can be deleted altogether.

Binding with Map API Extensions for Windows Phone 8

I'm trying to use databinding with the map api extension of windows phone toolkit. I'm doing :
<maps:Map x:Name="Map" Center="47.6, -122.3" ZoomLevel="12">
<maptk:MapExtensions.Children>
<maptk:MapItemsControl ItemsSource="{Binding PositionList}">
<maptk:MapItemsControl.ItemTemplate>
<DataTemplate>
<maptk:Pushpin GeoCoordinate="{Binding}" />
</DataTemplate>
</maptk:MapItemsControl.ItemTemplate>
</maptk:MapItemsControl>
</maptk:MapExtensions.Children>
</maps:Map>
with my code behind :
public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string name= null)
{
if (object.Equals(variable, valeur)) return false;
variable = valeur;
NotifyPropertyChanged(name);
return true;
}
private IEnumerable<GeoCoordinate> positionList;
public IEnumerable<GeoCoordinate> PositionList
{
get { return positionList; }
set { NotifyPropertyChanged(ref positionList, value); }
}
public MainPage()
{
InitializeComponent();
PositionList = new List<GeoCoordinate>
{
new GeoCoordinate(47.6050338745117, -122.334243774414),
new GeoCoordinate(47.6045697927475, -122.329885661602),
new GeoCoordinate(47.605712890625, -122.330268859863),
new GeoCoordinate(47.6015319824219, -122.335113525391),
new GeoCoordinate(47.6056594848633, -122.334243774414)
};
DataContext = this;
}
}
But I can't see any pushpin on the map :(
What am I doing wrong ?
Note that If I use this in the code-behind file, it's working
MapExtensions.GetChildren(Map).OfType<MapItemsControl>().First().ItemsSource = PositionList;
Thanks in advance for your help,
Best regards
MapItemsControl derives from DependencyObject, not FrameworkElement so the DataContext does not propagate. Long story long... you can't data bind MapItemsControl from XAML unless you have some way to set the Source property of the Binding.
If the FindAncestor mode of RelativeSource worked on the phone, it might be possible to work around this but it apparently does not. This leaves us with either creating the binding in code or (more realistically) setting the ItemsSource in code.

Windows Metro Style app data binding

I have a requirement to load images from a folder in the project to a stackpanel. Under each image a name should also be shown. The image folder can change at any time and the number of images can also change.(with a maximum of 50 images) I want to know if I can use data binding to handle this. I thought of having image ID's, their sources and the name for each image in an XML so that I can change that XML file whenever the image folder changes, without changing the rest of the code. Is that feasible? If so how? Can someone please guide me? Thank you in advance.
One solution would be to use a Filepicker to let the user select the images inside the folder, and then bind the selected images to an Itemscontrol. That itemscontrol can then be put inside the Stackpanel. Here's a quick sample using that solution.
Here's the codebehind for picking the image files:
private List<EditableImage> availableImagesList = new List<EditableImage>();
private async void FilePicker_Clicked(object sender, RoutedEventArgs e)
{
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.ViewMode = PickerViewMode.List;
openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
//TODO: add supported image file types
openPicker.FileTypeFilter.Add("jpg,png,gif");
// We prompt the user to pick one or more files
IReadOnlyList<StorageFile> files = await openPicker.PickMultipleFilesAsync();
if (files.Count > 0)
{
availableImages.DataContext = null;
String fp = ""; // The path of the picked image
int index = availableImagesList.Count;
foreach (StorageFile file in files)
{
// Copying the selected image to local app data folder
//TODO: check if the selected file is actually and image
if (file != null )
{
StorageFile fileCopy = await file.CopyAsync(ApplicationData.Current.LocalFolder, file.DisplayName + file.FileType, NameCollisionOption.ReplaceExisting);
fp = fileCopy.Path;
}
//Creating the image
CustomImage picToAdd = new CustomImage(index+1, file.DisplayName, fp);
//Adding the image as an UI element to the app bar
availableImagesList.Add(picToAdd);
}
availableImages.DataContext = availableImagesList;
}
}
The CustomImage model:
public class CustomImage
{
private static Uri _baseUri = new Uri("ms-appx:///");
private int _id;
public int Id
{
get { return _id; }
set
{
this.SetProperty(ref this._id, value);
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
this.SetProperty(ref this._name, value);
}
}
private string _imgPath;
public string ImgPath
{
get { return _imgPath; }
set
{
this.SetProperty(ref this._imgPath, value);
}
}
private String _imagePath = null;
private ImageSource _image = null;
public ImageSource Image
{
get
{
if (this._image == null && this._imagePath != null)
{
this._image = new BitmapImage(new Uri(CustomImage._baseUri, this._imagePath));
}
return this._image;
}
set
{
this._imagePath = null;
this.SetProperty(ref this._image, value);
}
}
public void SetImage(String path)
{
this._image = null;
this._imagePath = path;
this.OnPropertyChanged("Image");
}
public CustomImage(int id, string name, string imagepath)
{
SetImage(imagepath);
_id = id;
_name = name;
}
}
Here's the XAML for the ItemsControl inside the Stackpanel:
<StackPanel x:Name="loadedImages" HorizontalAlignment="Left" Orientation="Horizontal">
<!--Displaying the selected images in stackpanel-->
<ItemsControl ItemsSource="{Binding}" ItemsPanel="{StaticResource LoadedItemsPanel}">
<ItemsControl.ItemTemplate>
<!--The template for each object that is displayed as an UI element-->
<DataTemplate>
<Grid Height="88" Margin="2,0" Width="88" >
<Image Source="{Binding Image}"/>
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
In your page resources, you must also define:
<ItemsPanelTemplate x:Key="LoadedItemsPanel">
<WrapGrid Orientation="Horizontal"/>
</ItemsPanelTemplate>

Resources