问题
I have following code:
Page with lots of images, that are loaded dynamically with the databinding:
base.OnAppearing();
if (!loaded)
{
loaded = true;
BindingContext = new GalleryViewModel(pCode, gCode, gUrl);
}
viewmodel:
namespace GalShare.ViewModel
{
class GalleryViewModel
{
public string pCode { get; set; }
public string gCode { get; set; }
public string gUrl { get; set; }
public ObservableCollection<picdata> Galleries { get; set; }
public GalleryViewModel(string pCode, string gCode, string gUrl)
{
this.pCode = pCode;
this.gCode = gCode;
this.gUrl = gUrl;
Galleries = new GalleryService().GetImageList(pCode,gCode,gUrl);
}
}
}
galleryservice.cs
class GalleryService
{
public ObservableCollection<picdata> Images { get; set; }
public ObservableCollection<picdata> GetImageList(string pCode, string gCode, string gUrl)
{
WebClient client = new WebClient();
Images = new ObservableCollection<picdata>();
string downloadString = client.DownloadString(gUrl);
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;
}
}
XAML of the page:
<?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"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
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>
<ffimageloading:CachedImage Source="{Binding ThumbUrl}" CacheDuration="1" HorizontalOptions="Fill" VerticalOptions="Fill" DownsampleToViewSize="False"></ffimageloading:CachedImage>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
The code works, but given that the images are loaded from the web, while I am loading the data the App is locked. How can this be done asynchronously? I'd like to load the destination page, and then load the content while I'm there.. Currently the app freezes on the page that loads this one until all is loaded.
I have tried with tasks/await but with no success. I think i have to move some things around to run the code asynchronously but cannot figure out how.
回答1:
You've tagged async-await
and writting asynchronous in your title. However, all of your code is running on the main thread and not asynchronously.
Instead of loading your data in the constructor of the ViewModel. I highly suggest you use a lifecycle event such as OnAppearing
on your Page
and fire a ICommand
to load your data asynchronously.
Additionally I would switch to using HttpClient
and its nice async APIs. So something like:
public class GalleryService
{
private HttpClient _httpClient;
public GalleryService()
{
_httpClient = new HttpClient();
}
public async Task<IEnumerable<picdata>> GetImageList(string pCode, string gCode, string gUrl)
{
var response = await _httpClient.GetAsync(gUrl).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
var deserialized = JsonConvert.DeserializeObject<JsonTxt>(json);
var images = new List<picdata>();
foreach(var 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;
}
return new picdata[0]; // return empty set
}
}
and ViewModel:
public class GalleryViewModel
{
private GalleryService _galleryService;
public ObservableCollection<picdata> Galleries { get; } = new ObservableCollection<picdata>();
public ICommand GetImagesCommand { get; }
public GalleryViewModel(string pCode, string gCode, string gUrl)
{
_galleryService = new GalleryService();
GetImagesCommand = new Command(async () => DoGetImagesCommand(pCode, gCode, gUrl));
}
private async Task DoGetImagesCommand(string pCode, string gCode, string gUrl)
{
var images = await _galleryService.GetImageList(pCode, gCode, gUrl);
foreach(var image in images)
Galleries.Add(image);
}
}
Then in your OnAppearing()
override on your page you can call something like: (BindingContext as GalleryViewModel).GetImagesCommand.Execute(null);
Make sure you set your BindingContext
before trying to call the command. This can be done in the Page constructor with:
BindingContext = new GalleryViewModel();
This way you are not blocking your entire UI until it is done downloading the images. Alternatively you could fire off a Task with Task.Run
in the constructor of the ViewModel. However, then you will have to marshal the population of the ObservableCollection to the UI thread.
来源:https://stackoverflow.com/questions/60804577/xamarin-asynchronous-data-binding