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>
Related
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
I am using Prism library to implement bind Image with BitmapImage.
This is xaml
<Button Grid.Row="1" Text="Resize Img" Command="{Binding ImageResize}"></Button>
<Image Grid.Row="2" Source="{Binding ImageResult}"></Image>
<Image Grid.Row="3" Source="{Binding ImageUri}"></Image>
<Label Text="{Binding ImageUri}"></Label>
This is ImagePageViewModel
public class ImagePageViewModel : BindableBase
{
public ImagePageViewModel()
{
}
private string _imageUri;
public string ImageUri
{
get { return _imageUri; }
set { SetProperty(ref _imageUri, value); }
}
private BitmapImage _imageResult;
public BitmapImage ImageResult
{
get { return _imageResult; }
set { SetProperty(ref _imageResult, value); }
}
private DelegateCommand _imageSelect;
public DelegateCommand ImageSelect =>
_imageSelect ?? (_imageSelect = new DelegateCommand(ExecuteImageSelect));
async void ExecuteImageSelect()
{
try
{
var picker = new Windows.Storage.Pickers.FileOpenPicker
{
ViewMode = Windows.Storage.Pickers.PickerViewMode.Thumbnail,
SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary
};
picker.FileTypeFilter.Add(".jpg");
picker.FileTypeFilter.Add(".jpeg");
picker.FileTypeFilter.Add(".png");
Windows.Storage.StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
// Set the image source to the selected bitmap
BitmapImage bitmapImage = new BitmapImage();
//bitmapImage.DecodePixelWidth = 600; //match the target Image.Width, not shown
await bitmapImage.SetSourceAsync(fileStream);
ImageResult = bitmapImage;
ImageUri = file.Path;
//ImageUri = #"http://pic26.nipic.com/20121221/9252150_142515375000_2.jpg";
}
}
else
{
ImageUri = "";
}
}
catch (Exception ex)
{
throw ex;
}
}
}
For above code, setting ImageResult and setting ImageUri with file.Path both are not work. The file.Path return the right value for the file.
It only works when using with network file.
Is this related with File access permission if you browser the local disk file?
I assume this is related with the UWP File permission, but any idea how to resolve it?
The way that setting ImageResult works well for UWP platform. But set image source with file.Path will not work. UWP run in sand box, we could not access file with path in the xaml. I check your code, it does not look like uwp project. because UWP does not contain Label control. If the project is xamarin, please refer xamarin image document
Update
The Froms project is shared library, you should not call UWP FileOpenPicker in it. For your requirement, you could create Dependency Service for Xamarin and convert Bitmap to Xamarin image source for more please refer this case.
I want to change my label text value of the previous page when back button pressed from the new page. I use messagecenter feature from new page and redirect the code flow to the RefreshCustomerDetails() in viewmodel.
I tried like below.
string _fullname = "";
public string FullName
{
protected set
{
if (_fullname != value)
{
_fullname = value;
OnPropertyChanged("FullName");
}
}
get { return _fullname; }
}
public void RefreshCustomerDetails()
{
//FullName = null;
FullName = Application.Current.Properties["customerFullName"].ToString();
}
<Label
x:Name="title_label"
Text="{Binding FullName}"
Font="Bold,18"
TextColor="Black"
Margin="-20,0,0,0"
HorizontalOptions="CenterAndExpand"
VerticalOptions="Center"/>
Taking the Fullname value from Local db and Bind it like above code, but no change in the name when back buttoon press. Try assign null value, that is also not working.
Any correction on my code?
Your code looks good, it should work. Does your ViewModel inherate from INotifyPropertyChanged? This is required for OnPropertyChanged():
public class MyViewModel : INotifyPropertyChanged
{
// stuff ...
}
Updated the code like below, remove RefreshCustomerDetails()
and add Device.BeginInvokeOnMainThread inside of MessagingCenter.
Text="{Binding FullName,Mode=TwoWay}"
MessagingCenter.Subscribe<AddCustomerBySOPage>(this, "Refresh", (sender) =>
{
Device.BeginInvokeOnMainThread(() =>
{
FullName = Application.Current.Properties["customerFullName"].ToString();
});
});
This is a follow up from my previous question (UWP Problems Setting GridView Items source using x:Bind).
In that case I was using the random access file data source from Mirosoft's Data virtualization sample and the grid was never being populated. The problem there was that I was not raising the property changed event. The grid now works well.
My problem now is that instead of a grid view I am trying to use the data source in a flip view control. The data source does get initialized, the change property notification is raised but nothing is shown in the flip view.
Can a flip view display data from a virtual collection?
Here is my model's code:
public class PhotoFlipViewViewModel : Mvvm.ViewModelBase
{
private FileDataSource _PicturesCollection;
public FileDataSource PicturesCollection
{
get
{
return _PicturesCollection;
}
set
{
if (_PicturesCollection != value)
{
_PicturesCollection = value;
RaisePropertyChanged(() => PicturesCollection);
}
}
}
public PhotoFlipViewViewModel()
{
initdata();
}
async void initdata()
{
StorageLibrary pictures = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
string path = pictures.SaveFolder.Path;
var source = await FileDataSource.GetDataSoure(path);
if (source.Count > 0)
{
PicturesCollection = source;
}
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> suspensionState)
{
System.Diagnostics.Debug.WriteLine("PhotoFlipViewViewModel::OnNavigatedToAsync - event fired.");
if ( SessionState.ContainsKey("SelectedPhotoObjectFromGrid"))
{
await Task.CompletedTask;
}
public override async Task OnNavigatedFromAsync(IDictionary<string, object> suspensionState, bool suspending)
{
if (suspending)
{
}
await Task.CompletedTask;
}
public override async Task OnNavigatingFromAsync(NavigatingEventArgs args)
{
args.Cancel = false;
await Task.CompletedTask;
}
}
My FlipView in XAML:
<FlipView x:Name="PhotoOuterFlipView"
BorderBrush="Black"
BorderThickness="1"
VerticalAlignment="Top"
HorizontalAlignment="Stretch"
ItemsSource="{x:Bind ViewModel.PicturesCollection, Mode=OneWay}"
ItemTemplate="{StaticResource PictureFlipViewTemplate}">
<FlipView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</FlipView.ItemsPanel>
</FlipView>
Thanks, any advice is appreciated.
How can I databind an image using XAML?
Specifically, I have a stream object that I would like to convert into an image.
However, I just cannot find a basic example of how to do this.
1. What does the viewmodel property look like?
Do I use an ImageSource?
Do I need a value converter?
2. What does the XAML look like?
Forms has a StreamImageSource you can use to do this. However, I don't think you can use it in XAML without writing a custom converter. To use it in code, you would do something like this:
image1.Source = ImageSource.FromStream(() =>
{
// whatever you need to do to create your stream
return stream;
});
The demo illustrates the binding via code. For a XAML implementation you need something along the lines of:-
<Image Source="{Binding MyImageAsBytes, Converter={StaticResource MyByteToImageSourceConverter}}" />
Once you have your byte[] in your ViewModel, you will need a converter to convert this byte array, that contains the image, to a Xamarin.Forms ImageSource.
The converter, takes the byte[] array and converts to an ImageSource via:-
objImageSource = ImageSource.FromStream(() => new MemoryStream(bytImageData));
Example:-
StackLayout objStackLayout = new StackLayout();
byte[] bytImage = { your image as a byte collection }
this.BindingContext = new MyImageViewModel()
{
MyImageAsBytes = bytImage
};
Image objImage = new Image();
objImage.SetBinding(Image.SourceProperty, "MyImageAsBytes", converter: new MyByteToImageSourceConverter());
objStackLayout.Children.Add(objImage);
ViewModel:-
public class MyImageViewModel
: Xamarin.Forms.View
{
public static readonly BindableProperty MyImageAsBytesProperty = BindableProperty.Create<MyImageViewModel, byte[]>(p => p.MyImageAsBytes, default(byte[]));
public byte[] MyImageAsBytes
{
get { return (byte[])GetValue(MyImageAsBytesProperty); }
set { SetValue(MyImageAsBytesProperty, value); }
}
}
Converter:-
public class MyByteToImageSourceConverter
: IValueConverter
{
public object Convert(object pobjValue, Type pobjTargetType, object pobjParameter, System.Globalization.CultureInfo pobjCulture)
{
ImageSource objImageSource;
//
if (pobjValue != null)
{
byte[] bytImageData = (byte[])pobjValue;
//
objImageSource = ImageSource.FromStream(() => new MemoryStream(bytImageData));
}
else
{
objImageSource = null;
}
//
return objImageSource;
}
public object ConvertBack(object pobjValue, Type pobjTargetType, object pobjParameter, System.Globalization.CultureInfo pobjCulture)
{
throw new NotImplementedException();
}
}