In XAML & C# using Xamarin.Forms (iOS Project) I'm trying to create a gallery where the user can add photos to it. Currently I can show a list that does have the photo data binded to each entry because the user can click on an item in the list and the correct image will show up.. However I have not been successful in actually displaying a smaller version of the pictures in my FlowListView. I know it has to be something with the binding and I'm trying to grab the image uri from each object to display it but I'm still pretty new to this, especially to xaml and haven't been successful with this part yet.
If you could point me in the right direction that would be sweet!
Expected Result
To display images in 2 columns using FlowListView and FFImageLoading
Actual Result
I currently an able to display a 2 column list that has the right objects tied to each item but the only way I can actually see if anything is there is if I add a frame or add a text label to each item.
The user can click on each label currently and the correct image will show.
This is part of my TicketPageModel
private void AddItems()
{
public void UpdatePhotosData()
{
//get the notes and set the source for the list to them.
photos = NLTicketPhotos.Get(_ticket).OrderByDescending(x => x.PhotoCreatedAt).ToList();
}
foreach (var i in photos)
{
Items.Add(i);
}
}
private ObservableCollection<NLTicketPhoto> _items = new ObservableCollection<NLTicketPhoto>();
public ObservableCollection<NLTicketPhoto> Items
{
get
{
return _items;
}
set
{
if (_items != value)
{
_items = value;
OnPropertyChanged(nameof(Items));
}
}
}
My XAML
<flv:FlowListView FlowColumnCount="2" SeparatorVisibility="Default" HasUnevenRows="True" FlowItemTapped="OnImageTapped" FlowItemsSource="{Binding Items}">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<StackLayout Padding="10" Spacing="0" AutomationProperties.Name="Too Cool">
<ff:CachedImage Aspect="AspectFill" HeightRequest="30">
<ff:CachedImage.Source>
<UriImageSource Uri="{Binding Items.PhotoFileUrl}"/>
</ff:CachedImage.Source>
</ff:CachedImage>
<StackLayout Orientation="Horizontal" Padding="10" Spacing="0">
<Label HorizontalOptions="Fill" VerticalOptions="Fill" TextColor="Black" XAlign="Center" YAlign="Center" Text="Too Cool For School"/>
</StackLayout>
</StackLayout>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
My Code Behind
void OnImageTapped(object sender, ItemTappedEventArgs e)
{
NLTicketPhoto photo = (NLTicketPhoto)e.Item;
//listPhotos.SelectedItem = null; //deselect the item
switch (photo.PhotoFileType)
{
case "mov":
if (photo.PhotoIsOnServer)
{
Device.OpenUri(new Uri(photo.PhotoFileName));
}
else
{
//Only watch videos after sync
DisplayAlert("Video Unavailable", string.Format("This video will be viewable after it is synced to the server."), "OK");
}
break;
case "jpg":
//View image
NLPageViewPhoto preview = new NLPageViewPhoto(photo);
Navigation.PushAsync(preview);
break;
default:
DisplayAlert("Photo Unavailable", string.Format("The photo format .{0} is currently not viewable", photo.PhotoFileType), "OK");
break;
}
}
Once I implemented INotifyPropertyChanged to my NLTicketPhoto model and tweaked my PhotoUrl and PhotoDescription properties to use OnPropertyChanged() I was able to get my list to display properly.
Following this example helped me. https://www.c-sharpcorner.com/article/grid-view-in-xamarin-forms-using-flowlistview/
Related
I have created custom tab control using ScrollView control and Bindable StackLayout control.
I have first created this solution in Xamarin.Forms (VS for Mac 2019) and it works fine in both platforms, but the same solution when developed in .Net MAUI (VS for Mac 2022 Prev) it's not working properly in Android.
Update 30 Jun 2022
There is an issue with BindableLayout (StackLayout) properties in MAUI currently so when we are changing values it does not get reflected, and because of this, I think I'm facing this issue. Here is the reference
Here is what I have done so far:
MainPage.xaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:poc_maui.ViewModels"
x:Class="poc_maui.Views.HomePage"
xmlns:tabs="clr-namespace:poc_maui.Views.SubViews"
Title="HomePage">
<ContentPage.BindingContext>
<vm:MainPageViewModel />
</ContentPage.BindingContext>
<Grid RowDefinitions="50, *" RowSpacing="0">
<ScrollView Grid.Row="0" Orientation="Horizontal" VerticalOptions="Start" HorizontalScrollBarVisibility="Never"
Scrolled="ScrollView_Scrolled">
<StackLayout x:Name="TabsView"
Orientation="Horizontal"
BindableLayout.ItemsSource="{Binding Tabs}" Spacing="0">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Grid RowDefinitions="*, 4" RowSpacing="0">
<Label Grid.Row="0"
Text="{Binding TabTitle}"
TextColor="White"
BackgroundColor="navy"
Padding="20,0"
VerticalTextAlignment="Center"
HorizontalTextAlignment="Center"
FontSize="12"
HeightRequest="40"/>
<BoxView Grid.Row="1"
Color="Yellow"
IsVisible="{Binding IsSelected}"/>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Path=BindingContext.TabChangedCommand,
Source={x:Reference TabsView}}"
CommandParameter="{Binding .}"/>
</Grid.GestureRecognizers>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</ScrollView>
<tabs:ParentRecordTabView Grid.Row="1" IsVisible="{Binding IsParentRecordTabVisible}"
VerticalOptions="FillAndExpand"/>
<tabs:AdditionalInfoTabView Grid.Row="1" IsVisible="{Binding IsAdditionalInfoTabVisible}"
VerticalOptions="FillAndExpand" />
</Grid>
</ContentPage>
MainPageViewModel
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows.Input;
using poc_maui.Models;
namespace poc_maui.ViewModels
{
public class MainPageViewModel : BaseViewModel
{
#region Constructor
public MainPageViewModel()
{
GetTabs();
}
#endregion
#region Private Properties
private bool _isParentRecordTabVisible = true;
private bool _isAdditionalInfoTabVisible;
private ObservableCollection<TabViewModel> _tabs { get; set; }
#endregion
#region Public Properties
public bool IsParentRecordTabVisible
{
get => _isParentRecordTabVisible;
set { _isParentRecordTabVisible = value; OnPropertyChanged(nameof(IsParentRecordTabVisible)); }
}
public bool IsAdditionalInfoTabVisible
{
get => _isAdditionalInfoTabVisible;
set { _isAdditionalInfoTabVisible = value; OnPropertyChanged(nameof(IsAdditionalInfoTabVisible)); }
}
public ObservableCollection<TabViewModel> Tabs
{
get => _tabs;
set { _tabs = value; OnPropertyChanged(nameof(Tabs)); }
}
#endregion
#region Commands
public ICommand TabChangedCommand { get { return new Command<TabViewModel>(ChangeTabClick); } }
#endregion
#region Private Methods
private void GetTabs()
{
Tabs = new ObservableCollection<TabViewModel>();
Tabs.Add(new TabViewModel { TabId = 1, IsSelected = true, TabTitle = "Parent record" });
Tabs.Add(new TabViewModel { TabId = 2, TabTitle = "Additional Info" });
Tabs.Add(new TabViewModel { TabId = 3, TabTitle = "Contacts" });
Tabs.Add(new TabViewModel { TabId = 4, TabTitle = "Previous inspections" });
Tabs.Add(new TabViewModel { TabId = 5, TabTitle = "Attachments" });
SelectedTab = Tabs.FirstOrDefault();
}
private void ChangeTabClick(TabViewModel tab)
{
try
{
var tabs = new ObservableCollection<TabViewModel>(Tabs);
foreach (var item in tabs)
{
if (item.TabId == tab.TabId)
{
item.IsSelected = true;
}
else
{
item.IsSelected = false;
}
}
Tabs.Clear();
Tabs = new ObservableCollection<TabViewModel>(tabs);
switch (tab.TabId)
{
case 1:
IsParentRecordTabVisible = true;
IsAdditionalInfoTabVisible = false;
break;
case 2:
IsParentRecordTabVisible = false;
IsAdditionalInfoTabVisible = true;
break;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
#endregion
}
}
#ParentTabView.xaml
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="poc_maui.Views.SubViews.ParentTabView">
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="CenterAndExpand" >
<Label
Text="Welcome to Parent tab!"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentView>
#AdditionalInfoTabView.xaml
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="poc_maui.Views.SubViews.AdditionalInfoTabView">
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="CenterAndExpand" >
<Label
Text="Welcome to Additiona info tab!"
VerticalOptions="Center"
HorizontalOptions="Center" />
</StackLayout>
</ContentView>
So what happens here in Android is when I'm clicking AdditionalInfo Tab then it will show a blank white screen and if you press the hardware back button and open the app again it will show AdditionalTab as selected and its views content as well.
If I remove switch() code part from the ViewModel then it will work fine but tabs will not change. Does anyone have idea about this kind of behavior of scroll view in MAUI?
The full source code is here: maui_sample
Does this work-around fix it?
MainPage.xaml:
<ScrollView x:Name "theScrollView" ... >
MainPage.xaml.cs:
public MainPage()
{
InitializeComponent();
MessagingCenter.Subscribe<MainPageViewModel>(this, "update", (sender) =>
{
// Tell theScrollView to re-layout its contents.
(theScrollView as IView).InvalidateArrange();
});
}
MainPageViewModel:
private void ChangeTabClick(TabViewModel tab)
{
... make changes ...
MessagingCenter.Send<MainPageViewModel>(this, "update");
}
MAYBE:
I'm not sure if MessagingCenter Subscribe is on Dispatcher (Main) thread. To be reliable, do:
MessagingCenter.Subscribe<MainPageViewModel>(this, "update", (sender) =>
{
Dispatcher.Dispatch( () =>
{
(theScrollView as IView).InvalidateArrange();
});
}
UPDATE
There are other Maui bugs, that have a common "theme": Maui on Android does "something" related to layout only once - at the time the page is first drawn. UNFORTUNATELY, anything that is "not visible" at that time, is skipped. And won't work when later made visible.
Until such bugs are fixed, you'll have to do some work-around.
WORK-AROUND #1:
Start with ALL tabs IsVisible="True".
As soon as the page has been drawn the first time, in code-behind, create the desired Bindings on those IsVisible properties. Page drawn first time can be intercepted in a custom handler. But this is a temp work-around, so its easier to just run a method after a 250 ms delay. Use a boolean "flag" to make the method only run the first time.
Might have to do InvalidateArrange as shown above, to force the Bindings to function the first time.
OR WORK-AROUND #2:
Each time tab changes, use shell route to go to MainPage again. Keep same view model, so knows which tab to show first (and remembers any other state you care about).
Both of these are ugly.
I recommend creating an issue at .Net Maui github, and providing link to your github sample.
This is still not works for me properly but after looking at below two links I found that it it not what we are looking for. The Isvisible : false first and then on switch or check box change you are trying to make it visible then it will not visible but the actual control visible. So on look after I have see this link but again the answer is not what I was looking for.
Step to resolve.
On View use the Parent as ScrollView or control belongs to IView,IElement.
<ScrollView x:Name "myScrollView">
.....
...
Add Action on ViewModel
public delegate void Action(T obj);
Invoke the Action
Note: Make sure you call this on require not all the time.
e.g. On Visibility set in ViewModel call after visibility update.
MeasureAction?.Invoke("reSetVisibility");
Now on View's Code File, use Viewmodel and accept the invoke
Here Call the below line will works perfectly.
(myScrollView as IView).InvalidateMeasure();
That's IT... Enjoy IsVisible now and make your layout as require.
I am using Xamarin.Forms, using MVVM. When I was building the View, I used images embedded in the App, using ImageSource.FromResource, and everything was fine, all of my images loaded correctly in the view. When I changed the source to use a URI from my local webserver, only the bottom 3 images load on the view.
I have verified that all of the images exist on the server, and that the URI sent to the image source is correct.
I am not sure why only 3 images get loaded and the rest are blank.
Here is my code:
ViewModel:
private ObservableCollection<KnownBird> _knownBirds { get; set; }
public ObservableCollection<KnownBird> KnownBirds
{
get
{
return _knownBirds;
}
set
{
_knownBirds = value;
OnPropertyChanged("KnownBirds");
}
}
public KnownBirdsViewModel()
{
Title = "Known Birds";
KnownBirds = new ObservableCollection<KnownBird>();
LoadKnownBirdsCommand = new Command(async () => await ExecuteLoadBirdsCommand());
}
async Task ExecuteLoadBirdsCommand()
{
if (IsBusy)
return;
IsBusy = true;
try
{
KnownBirds.Clear();
var _birds = await BirdService.GetKnownBirdsAsync();
foreach (var tmpBird in _birds)
{
KnownBird tmpKB = new KnownBird();
tmpKB.BirdID = tmpBird.BirdID;
tmpKB.Name = tmpBird.Name;
tmpKB.examplePicture = tmpBird.examplePicture;
//Change this to URL when setting this from the REST API and not the local data.
//tmpKB.BirdImage = ImageSource.FromResource("BirdWatcherMobileApp.SampleData.images." + tmpBird.examplePicture);
tmpKB.BirdImage = ImageSource.FromUri(new Uri("http://" + Settings.ServerAddress + "/images/bird_examples/" + tmpBird.examplePicture));
KnownBirds.Add(tmpKB);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
View:
<flv:FlowListView
FlowColumnCount="3"
HasUnevenRows="True"
FlowItemsSource="{Binding KnownBirds, Mode=OneWay}"
FlowItemTapped="FlowListView_FlowItemTapped"
IsPullToRefreshEnabled="True"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
RefreshCommand="{Binding LoadKnownBirdsCommand}">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<StackLayout Orientation="Vertical" Margin="10" HeightRequest="125">
<Image Source="{Binding BirdImage, Mode=OneWay}" HeightRequest="100" />
<Label
Text="{Binding Name}"
FontSize="16"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Start"
HorizontalOptions="CenterAndExpand" />
</StackLayout>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
Right now I am thinking that loading the images from a webserver is taking time, and once the images are loaded into the Collection, and once the binding occurs the ListView never updates the binding and the images are not loaded.
I've read that when using an ObservableCollection, the OnPropertyChanged event is only fired when items are added or removed from the collection, and NOT when data inside an item is updated.
I have that a BindingList collection will raise an OnPropertyChanged event when data inside an item in the list is updated. I've tried to swap out the ObservableCollection with a BindingList, but then noting is loaded into the view.
I am not sure how to proceed from here. Are my suspicions correct? Any help or information here would be greatly appreciated!
I would suggest you use FFImageLoading's CachedImage for this.
It is a library that is vastly accepted by the community and is great with caching and has memory handling options as well.
You can check their Git wiki to understand the library in depth.
Download it form Nuget
Call CachedImageRenderer.Init() on each platform. Let’s put it on MainActivity.cs of our Android project and AppDelegate.cs of iOS.
Then add its namespace and use it like this:
<ffimageloading:CachedImage
Source="{Binding BirdImage, Mode=OneWay}" HeightRequest="100" />
For more information you can check my blog here
It turns out this may have been due to Xamarin caching empty images. I was finally able to get all of them to display by using this line:
tmpKB.BirdImage = new UriImageSource { CachingEnabled = false, Uri = new Uri("http://" + Settings.ServerAddress + "/images/bird_examples/" + tmpBird.examplePicture) };
and setting CachingEnabled to false.
I have this method:
private async Task DisplayVideos(string query)
{
var videoObj = await _mediaService.GetVideos(query);
Videos = videoObj.Hits;
var size = "640x360";
var picIdList = videoObj.Hits.Select(x=>x.Picture_id).ToList();
foreach (var pic in picIdList)
{
PictureId = $"https://i.vimeocdn.com/video/{pic}_{size}.jpg";
}
}
This method hits an api endpoint and gets back a video object. I want to get the picture_id from the video object and manually set a size, then input the parameters into a specific url and then set the binding context of my image in my xaml to that specific url as I'm doing above.
However, the issue above is I'm setting PictureId to the last url in that list. How can I fix it so that a list of picture ids and in my listview, I have a image bind for every cell. I'm using FlowListView layout.
Here's my xaml:
<flv:FlowListView FlowColumnCount="2" SeparatorVisibility="None" HasUnevenRows="true"
FlowItemTappedCommand="{Binding ItemTappedCommand}"
FlowItemsSource="{Binding Videos}" >
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<Image Aspect="AspectFill" HeightRequest="200" Source="{Binding PictureId}" />
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
Let's assume that GetVideos() returns a List<Video>, and that Video is a class that you've defined in your code, so you can modify it.
Just add a read-only Property to the Video class that will return the url for the image
public string PictureUrl {
get {
$"https://i.vimeocdn.com/video/{Picture_Id}_640x360.jpg";
}
}
I have built a app using Xamarin.Forms that I am displaying in Windows.
Requirement:
- tap on item in the ListView >
- the displayed value for the item changes in the UI
Here is the relevant xaml
NOTE: I have set the ListView.ItemsSource from code behind
<ListView.ItemTemplate >
<StackLayout VerticalOptions="Center" HorizontalOptions="Center" >
<Label Text="Passo dopo passo" VerticalOptions="Center" HorizontalOptions="Center" />
<Button Clicked="GetGroceries" Text="Get Groceries" ></Button>
<ListView x:Name="lvGroceries" ItemTapped="GroceryItemTapped" >
<ListView.ItemTemplate >
Here is the c# code behind, which runs fine when the item is tapped, but the UI does not change.
public ObservableCollection oc;
public void GroceryItemTapped (object o, ItemTappedEventArgs e )
{
if (e.Item == null ) {
return;
}
var g = ((GroceryItem)e.Item);
// oc is populated and exactly what I expect here
foreach (var gr in oc)
{
// the next line grabs the item I want
if (gr.GroceryId == g.GroceryId)
{
gr.GroceryName = "snot";
} } }
// when the above code runs, the data is already in oc and this code ran
lvGroceries.ItemsSource = oc;
Ideas?
You need to bind your ListView to your collection:
<ListView x:Name="lvGroceries" ItemsSource="{Binding oc}" ItemTapped="GroceryItemTapped" >
You also need to set your ContentPage.BindingContext. You also need to implement INotifyPropertyChanged and run OnPropertyChanged() anytime your collection is updated. Finally, you need to add to your collection by doing oc.Add() instead of reseting the ItemSource every time because that will overwrite your binding.
Highly suggest looking through the Xamarin Forms Binding Documentation.
I'm trying to implement a ListView in Xamarin Forms. A list that we can check or choose the item that we want. I want a single item selection at a time.
My xaml file :
ListView x:Name="listview" ItemSelected="OnItemSelected" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout HorizontalOptions="StartAndExpand" Orientation="Horizontal">
<StackLayout Padding="20,0,0,0" VerticalOptions="Center" Orientation="Vertical">
<Label Text="{Binding .}" YAlign="Center" FontSize="Medium" />
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My xaml.cs file :
public void OnItemSelected (object sender, SelectedItemChangedEventArgs e) {
if (e.SelectedItem == null) return;
// add the checkmark in the event because the item was clicked
// be able to check the item here
DisplayAlert("Tapped", e.SelectedItem + " row was tapped", "OK");
((ListView)sender).SelectedItem = null;
}
There is a better way to do it?
I want something like this without alphabet and search menu :
Take a look on this https://github.com/ricardoromo/Listview-Multiselect, I made this sample to simulate miltiselect listview.
You could try using XLabs checkbox or radioButton control, Here is a list of controls examples, you just need to download the package via NuGet Manager.
If you only need to check 1 item, in your tapped event make an action after item is tapped. Here's an example:
async void FriendListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
var el = e.Item as ProfileItem;
SelectedItem = el;
if (e.Item != null)
{
await Navigation.PushAsync(new FriendProfile(el));
}
((ListView)sender).SelectedItem = null; // de-select the row
}
I've tried the Multiselection listview in xamarin forms and I got it working for both iOS and Android.
This took me some time so I wrote a blog about the entire prototype. You can find it here :
http://androidwithashray.blogspot.com/2018/03/multiselect-list-view-using-xamarin.html
The blog gives you full info on creating the Listview with the checkbox and selecting multiple items. Hope this helps!!