I have a page built like this:
<StackLayout>
<CollectionView ItemsSource="{Binding Galleries}" x:Name="myCollection" SelectionMode="Single" SelectionChanged="CollectionView_SelectionChanged">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ThumbUrl}"
Aspect="AspectFit" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<ActivityIndicator BindingContext="{x:Reference myCollection}" IsRunning="{Binding IsLoading}"/>
</StackLayout>
The binding source returns a lot of URLs to images that are then displayed like a gallery. Since it takes a few seconds to load the page, I would like to show the activity indicator. I do have another page, that links to the various galleries. This is how The page loads up the gallery page:
private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.Count > 0)
{
var item = (GalleryListEntry)e.CurrentSelection.FirstOrDefault();
((CollectionView)sender).SelectedItem = null;
Navigation.PushModalAsync(new Gallery(item.PCode, item.GCode));
}
}
problem is that whenever I click on this page, it freezes, till the gallery is loaded and then jumps directly to the gallery. I would like it to display the gallery (empty) with the activity indicator, till the gallery is loaded, instead of freezing..
What am I doing wrong?
EDIT: as requested, code for Gallery page:
Gallery.Xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:GalShare.ViewModel"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="GalShare.Views.Gallery">
<StackLayout>
<CollectionView ItemsSource="{Binding Galleries}" x:Name="myCollection" SelectionMode="Single" SelectionChanged="CollectionView_SelectionChanged">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical"
Span="2" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Image Source="{Binding ThumbUrl}"
Aspect="AspectFit" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<ActivityIndicator BindingContext="{x:Reference myCollection}" IsRunning="{Binding IsLoading}"/>
</StackLayout>
gallery.xaml.cs
using GalShare.Model;
using GalShare.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace GalShare.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Gallery : ContentPage
{
public Gallery(string photographerCode, string galleryCode)
{
InitializeComponent();
BindingContext = new GalleryViewModel(photographerCode, galleryCode);
}
}
}
GalleryViewModel.cs
using GalShare.Model;
using GalShare.Service;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
namespace GalShare.ViewModel
{
class GalleryViewModel
{
public string pCode { get; set; }
public string gCode { get; set; }
public ObservableCollection<picdata> Galleries { get; set; }
public GalleryViewModel(string pCode, string gCode)
{
this.pCode = pCode;
this.gCode = gCode;
Galleries = new GalleryService().GetImageList(pCode,gCode);
}
}
}
GalleryService.cs
using GalShare.Model;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net;
using System.Text;
namespace GalShare.Service
{
public class JsonGalListTxt
{
public string Galurl { get; set; }
}
public class JsonTxt
{
public Settings Settings { get; set; }
public IList<File> Files { get; set; }
}
public class Settings
{
public string Path { get; set; }
}
public class File
{
public string file { get; set; }
}
public class galURL
{
public string galurl { get; set; }
}
class GalleryService
{
// public string pCode { get; set; }
// public string gCode { get; set; }
public ObservableCollection<picdata> Images { get; set; }
public ObservableCollection<picdata> GetImageList(string pCode, string gCode)
{
WebClient client = new WebClient();
string GalUrl = client.DownloadString("https://www.mypage.it/getUrl.php?pid=" + pCode + "&galid=" + gCode);
var deserializedUrl = JsonConvert.DeserializeObject<galURL>(GalUrl);
Images = new ObservableCollection<picdata>();
string downloadString = client.DownloadString(deserializedUrl.galurl);
var deserialized = JsonConvert.DeserializeObject<JsonTxt>(downloadString);
foreach (File img in deserialized.Files)
{
Images.Add(new picdata()
{
ImageName = img.file,
BaseUrl = deserialized.Settings.Path.ToString(),
ThumbUrl = deserialized.Settings.Path.ToString() + "/thumbs" + img.file
});
}
return Images;
}
}
}
when you create your VM from the constructor, it is loading ALL of the images on the main thread, blocking the page from actually appearing until it completes
public Gallery(string photographerCode, string galleryCode)
{
InitializeComponent();
BindingContext = new GalleryViewModel(photographerCode, galleryCode);
}
at a minimum, I would move this to OnAppearing, so that the page will display first, and then load the images.
You may also want to move the image requests to another thread so it doesn't block the UI. You could also use a library like FFImageLoading which helps manage this for you
Related
I have tried using the guide on using local databases and reading the working application (Tasky) source code to get an idea of how i could achieve this but my listview is still empty. Also i would like to know if it is fine to resolve the issue like this or should i create a viewmodel that with an observable collection filled with the results obtained from the database?
This is my code:
This is my model:
public class AppUser
{
[PrimaryKey, AutoIncrement]
public long AppUserId { get; set; }
[MaxLength(255)]
public string Name { get; set; }
[MaxLength(255)]
public string ContactName { get; set; }
public string Password { get; set; }
public string MedicalNumber { get; set; }
public string PoliceNumber { get; set; }
public string FireNumber { get; set; }
public string FamilyNumber { get; set; }
public string CustomTextMessage { get; set; }
}
This is my data access layer
public class AppUserDbConnect
{
static SQLiteAsyncConnection _database;
public AppUserDbConnect(string dbPath)
{
_database = new SQLiteAsyncConnection(dbPath);
_database.CreateTableAsync<AppUser>().Wait();
}
public Task<List<AppUser>> GetItemsAsync()
{
return _database.Table<AppUser>().ToListAsync();
}
public Task<List<AppUser>> GetItemsNotDoneAsync()
{
return _database.QueryAsync<AppUser>("SELECT * FROM [AppUser] WHERE [Done] = 0");
}
public Task<AppUser> GetItemAsync(int id)
{
return _database.Table<AppUser>().Where(i => i.AppUserId == id).FirstOrDefaultAsync();
}
public Task<int> SaveItemAsync(AppUser item)
{
if (item.AppUserId != 0)
{
return _database.UpdateAsync(item);
}
else
{
return _database.InsertAsync(item);
}
}
public Task<int> DeleteItemAsync(AppUser item)
{
return _database.DeleteAsync(item);
}
}
}
This is my view:
<StackLayout>
<Label Text="SETTINGS/MY Numbers" FontSize="30" FontAttributes="Bold"/>
<ListView x:Name="ListOfNumbers" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<Label Text="{Binding MedicalNumber}" />
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
And finally the code behind:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyNumbersPage : ContentPage
{
public MyNumbersPage ()
{
InitializeComponent( );
BindingContext = new AppUser();
}
protected async override void OnAppearing()
{
base.OnAppearing();
ListOfNumbers.ItemsSource = await App.AppUserDatabase.GetItemsAsync();
}
}
This is the add contacts page:
<StackLayout>
<Picker x:Name="picker" Title="Select a Country Code">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>+234</x:String>
<x:String>+264</x:String>
<x:String>+27</x:String>
<x:String>+266</x:String>
<x:String>+263</x:String>
<x:String>+218</x:String>
<x:String>+211</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
<Entry Placeholder="Enter the Contact Name" x:Name="Name"/>
<Entry Keyboard="Numeric" Placeholder="Enter your Number" x:Name="Number"/>
<Button Text="Cancel" Clicked="Cancel" />
<Button Text="Save" Clicked="AddNewContact"/>
</StackLayout>
and also the code-behind:
public partial class AddContactForFamily : ContentPage
{
public string pickerValue;
public AddContactForFamily ()
{
InitializeComponent ();
}
async void AddNewContact(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex != -1)
{
pickerValue = (string)picker.ItemsSource[selectedIndex];
}
var contact = new AppUser {ContactName = Name.Text, FamilyNumber = pickerValue +Number.Text };
if (App.AppUserDatabase.SaveItemAsync(contact) != null)
{
await DisplayAlert("Alert", "You have added a contact successful", "OK");
await Navigation.PopAsync();
}
}
async void Cancel(object sender, EventArgs e)
{
await Navigation.PopAsync();
}
}
I am trying to bind the values for a picker from a model in view model, there I am getting path of the model instead of the values.
<Picker x:Name="LocationPicker"
Title="Location" HeightRequest="40"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
SelectedItem="{Binding Location}"
ItemsSource="{Binding MaintainRoomTypes}"/>
Here is my view model code:
if (jobDetailsForFitter != null)
{
WindowDetails = new WindowDetailsModel
{
Windows = jobDetailsForFitter.Windows,
Locations = jobDetailsForFitter.Locations,
RoomTypes = jobDetailsForFitter.RoomTypes,
AddFiles = jobDetailsForFitter.AddFiles
};
Locations = jobDetailsForFitter.Locations;
MaintainRoomTypes = jobDetailsForFitter.RoomTypes;
await FitterService.Instance.LoadJobDetailsToLocalStore(jobDetailsForFitter, SelectedJob?.Id ?? 0);
}
how to bind itemsource to get list.
public List<Room> Locations { get; set; }
public List<RoomTypeModel> RoomTypes { get; set; }
You have to define ItemDisplayBinding property to Picker.
For eg:
public class Room
{
public string RoomNumber { private set; get; }
public string RoomName { private set; get; }
}
And you want to display RoomName in Picker
<Picker ItemsSource="{Binding Room}" ItemDisplayBinding="{Binding RoomName}"/>
Hope this will solve your problem.
Iam trying to bring the values of static variable from the following class
public class LoginUserId
{
public static int id { get; set; }
public static string username { get; set; }
}
from here i created another class to get values as following
public class UsersList
{
public static List<LoginUserId> Names { get; set; }
}
Now i have xaml page as
<ListView ItemSelected="LoginUserList_OnItemSelected" ItemsSource="{Binding Source={x:Static lacal:UsersList.Names}}" IsVisible="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding username}" TextColor="Black" FontSize="20" IsVisible="True"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
At output iam not getting anything just a blank page is appearing,please correct me where i went wrong.
You can't bind to static variables with the way you did it in your XAML. When you use Text="{Binding username}" your property has to be a non static Property public string username { get; set; } if you want to bind to your static property then you have to use the x:Static way.
<Label Text="{x:Static local:YourViewModel.username}" />
of course you have to add a namespace of your viewModels to the ContentPage
modify your code as below
public class LoginUserId
{
public int id { get; set; }
public string username { get; set; }
}
public class UsersList
{
public static List<LoginUserId> Names = new List<LoginUserId>() {
new LoginUserId{id = 1, username = "No1"},
new LoginUserId{id = 2, username = "No2"},
};
}
The List Names must be static , but the property of object in it should not be static , because we must initalize the variable for the model and assign the value for the properties, it's not steady.
void myButton_Click(object sender, RoutedEventArgs e)
{
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClient_DownloadStringCompleted);
webClient.DownloadStringAsync(new Uri("http://www.taxmann.com/TaxmannWhatsnewService/mobileservice.aspx?service=topstories"));
}
void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
var rootObject = JsonConvert.DeserializeObject<List<Contacts>>(e.Result);
}
public class Contacts
{
public string news_id { get; set; }
public string news_title { get; set; }
public string website_link { get; set; }
public string imagepath { get; set; }
public string news_date { get; set; }
public string news_detail_description { get; set; }
}
This is My C# code. I am able to count the Number of items var rootObject = JsonConvert.DeserializeObject<List<Contacts>>(e.Result); contain in response But i am Unable to Print data. Please help me how I can print. Please print atleast One item so that i can Understand
You have to prepare the UI in the XAML and set the databinding from the codebehind.
Example xaml code:
<ListBox Name="ListBoxNews" Margin="10,0,30,0" Height="486" Width="404" FontSize="20">
<ListBox.ItemTemplate>
<DataTemplate >
<StackPanel Margin="10,0,10,8">
<TextBlock Text="{Binding news_date}" TextWrapping="Wrap" FontSize="18" />
<TextBlock Text="{Binding news_title}" TextWrapping="Wrap" FontSize="24" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And databinding from codebehind.
void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
var rootObject = JsonConvert.DeserializeObject<List<Contacts>>(e.Result);
ListBoxNews.ItemsSource = rootObject;
}
Try this and ask me if you still have doubts.
I have these classes:
public class MovieExt
{
public string Title { get; set; }
public string Year { get; set; }
public List<string> Genres { get; set; }
public List<Actor> Actors { get; set; }
....
}
public class Actor
{
public string Name { get; set; }
public string Birth { get; set; }
public string Biography { get; set; }
public string Url { get; set; }
}
and this is my method in my page:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
object obj;
if (PhoneApplicationService.Current.State.TryGetValue("movie", out obj))
{
MovieExt movie = (MovieExt)obj;
this.DataContext = movie;
this.imgPoster.Source = new BitmapImage(new Uri(movie.PosterUrl, UriKind.Absolute));
}
base.OnNavigatedTo(e);
}
and in page I am binding properties like this:
<ListBox Grid.Row="4" Grid.Column="1"
Margin="5,5,5,5"
ItemsSource="{Binding Path=Actors }"
x:Name="listStars"/>
For everything it´s working (genres and others). Everything else is string. But for actors I want to bind in list name and after clicking on the actor I want to go to url. How can I bind name property from actor? Thanks
First of all, you need to create OnSelectedItemChanged event on your ListBox to handle clicks on your Actors.
Then you need to get your clicked item. You can do this in several ways. The simplest way is listBox.SelectedItem property.
And then your can get your Url with (listBox.SelectedItem as Actor).Url
Also, when you go back from details page, SelectedItem will be not null and click on the same item not fired event in second time. So, set SelectedItem to null when click is handled
UPD: to properly bind Actor class to ListBox you need to create ItemTemplate:
<ListBox ...>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text={Binding Name} />
<TextBlock Text={Binding Birth} />
<TextBlock Text={Binding Biography} />
<TextBlock Text={Binding Url} />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can override the ToString() method in the Actor class to display something friendly, like the name.
public override string ToString()
{
return Name;
}
This is useful when binding objects to comboboxes and dropdowns.