c# WinUI Task Cancellation involving Scrolling of BitampImage - c#

I am trying to make simple control to preview Images in the directory.
Layout is simple as below.
The thumbnails below is a scrollViewer. With left and right arrow keyDown the thumbnail gets Focus. Which fires an event and the Image is loaded in the Image control Above, with the follwing code.
var cancellationToken = getImageCTS.Token;
var Image = await imagesList[Index].GetImageSourceAsync(cancellationToken);
ImageInView.Source = Image;
Upon losing focus, the Task is cancelled with getImageCTS.Cancel() Which I don't know if it works.
imageeList is a ObservableCollection of type ImageViewerItem, with Following Code.
[ObservableObject]
internal partial class ImageViewerItem
{
private string path;
[ObservableProperty]
private BitmapImage bitmapImg = new();
private string name;
public ImageViewerItem(string name, string path)
{
this.path = path;
this.name = name;
GetThumbSourceAsync();
}
internal async Task<BitmapImage> GetImageSourceAsync(CancellationToken token)
{
try
{
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
BitmapImage bitmapImage = new();
var file = await StorageFile.GetFileFromPathAsync(path);
using IRandomAccessStream fileStream = await file.OpenReadAsync();
token.ThrowIfCancellationRequested(); ---> Sometimes it does get cancelled at this stage.
await bitmapImage.SetSourceAsync(fileStream);
return bitmapImage;
}
catch (OperationCanceledException)
{
return null;
}
}
}
Everything works works well so far.
Problem occurs if I hold down left/right key to scroll. The GetImageSourceAsync() does not get cancelled and Image is still loaded, which heavily impacts the scrolling.
If the item quickly loses focus, there is no need to load the Image in View. The task should be cancelled, But it runs to completion and scrolling starts stuttering.
I hope I am able to explain the problem here.

Related

How can I pop a navigation page when a value changes in a static class?

So I have a bit of a weird problem.
I've implemented a camera preview class (largely following this code here: https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/customrenderers-view/) but have added a button at the bottom to take a picture. This involves the use of both some xamarin forms code and some xamarin android code.
However, the CapturePage is only put on the stack when the user announces that they want to take a photo, and after the photo has been taken, I want to pop the Capture page to go back to the main screen. Currently, I have a static boolean value in the overall project that is changed from false to true when a capture has occurred. Is there some way to get my code in Main.xaml.cs to wait on this value changing, then pop whatever is on top of the navigation stack? Is this a use for a property changed? See code below:
The code in Project.Droid that handles the capturing and saving of the image:
void OnCapButtonClicked(object sender, EventArgs e)
{
// capButton.capture is an instance of Android.Hardware.Camera
capButton.capture.TakePicture(null, null, this);
// stop the preview when the capture happens
CameraInfoContainer.isPreviewing = false;
}
public void OnPictureTaken(byte[] data, Camera camera)
{
var filepath = string.Empty;
var clientInstanceId = Guid.NewGuid().ToString();
var saveLoc = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures);
filepath = System.IO.Path.Combine(saveLoc.AbsolutePath, clientInstanceId + ".jpg");
try
{
System.IO.File.WriteAllBytes(filepath, data);
//mediascan adds the saved image into the gallery
var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
mediaScanIntent.SetData(Android.Net.Uri.FromFile(new File(filepath)));
Forms.Context.SendBroadcast(mediaScanIntent);
}
catch (Exception e)
{
System.Console.WriteLine(e.ToString());
}
// CameraInfoContainer is a static class in Project NOT in Project.Droid
CameraInfoContainer.savedCapture = filepath;
CameraInfoContainer.capType = CaptureType.Photo;
CameraInfoContainer.captureComplete = true; // here is where I set the value (in Project)
}
Now the code in Project that pushes the capture page on the stack and that I want to trigger when the capture has happened:
// this method puts the capture page on the stack and starts the whole process
private async Task ExecuteNewCapture()
{
var cp = new CapturePage();
var np = new NavigationPage(cp);
await Navigation.PushModalAsync(np, true);
}
// this is the method that I want to trigger when a photo is taken (in Project/Main.xaml.cs)
private async Task Complete(string fileLoc)
{
await Navigation.PopModalAsync();
}
Answer is in a comment from Junior Jiang. Ended up using Xamarin.Forms.MessagingCenter to get done what I needed.

How to wait till a task is fully completed?

I have a image processing application in which I have 2 modes. Manual inspection and Automatic inspection using a PLC command.
Manual inspection works fine, the user clicks a button to grab an image, and then clicks another button to process the image and send results to the PLC.
But in automatic inspection mode, im getting incorrect inspection result(same product/image inspection in manual mode gets correct result). I suspect that after grabbing image the system is not getting enough time to read the full image before the inspection starts, so I added a thread.sleep(500), But that didnt make any difference. So I tried the async await.task.delay(500) method, same result.
Is there any other way to fix it?
Code:
private async void timer3_Tick(object sender, EventArgs e)
{
btncheckm1_Click(null, null);
var newSignal = textBox8.Text.Contains("+1");
var isRisingEdge = newSignal && (_oldSignal == false);
_oldSignal = newSignal;
if (isRisingEdge)
{
lblmessages.Text = "";
totalsheetcount++;
btngrabimage_Click(null, null);
// Thread.Sleep(300);
await Task.Delay(1000);
processimage();
}
}
btngrabimage() has the following code:
try
{
camimage = null;
cam1.BeginAcquisition();
// Retrieve an image
IManagedImage rawImage = cam1.GetNextImage();
IManagedImage convertedImage = rawImage.Convert(PixelFormatEnums.Mono8);
imagetoinspect = convertedImage.bitmap;
rawImage.Release();
cam1.EndAcquisition();
//cam1.DeInit();
distort();
}
catch (Exception ee)
{
}
I also would say that wrapping the grabbing of an image inside a task and awaiting that result is the way to go. The image class represents your return type as the image. Dont mind the function names though. Task.Run and Task.Factory.StartNew are essentially the same thing, just wanted to show you have different options for creating your task
public class ImageRetriever
{
public void ProcessImage()
{
//if you would like to get rid of the async
//reminder that calling .Result is blocking for the current thread.
//this means the thread will stop working untill the result is returned
//could be an issue if this is called on UI thead
var imageTask = GetImage();
Image image = imageTask.Result;
}
public async Task ProcessImageAsync()
{
//if you want to keep the async-nonblocking
Image image = await GetImage();
}
private Task<Image> GetImage()
{
//this is how your create your task
Task<Image> imageTask;
imageTask = Task.Run(() =>
{
return new Image();
});
// or
imageTask = Task.Factory.StartNew(() =>
{
return new Image();
});
return imageTask;
}
}
public class Image
{
}

WinRT preload images

I have a Windows 8.1 XAML app where I'd like to preload images before navigating between pages.
The naive code I have now is:
// Page1.xaml.cs
private void Button_Click(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(Page2));
}
And on the second page:
// Page2.xaml.cs
this.image1.Source = new BitmapImage(new Uri("ms-appx:///Assets/image1.jpg"));
this.image2.Source = new BitmapImage(new Uri("ms-appx:///Assets/image2.jpg"));
Now, when I click the button to navigate, I can see the images being loaded and showing up one at the time. I would like to pre-load the images on the button click and only navigate after the images have been loaded.
Unfortunately, just creating the BitmapImage objects and waiting for the ImageOpened events doesn't work. It appears that the images aren't loaded if they aren't rendered to the screen.
Is there a way to force loading of the images from code without adding them to the visual tree?
(Note: a workaround would be to actually add all the images to the Window in some invisible way, but I'd like to avoid that if at all possible)
Final solution:
This is what I've come up with using SetSourceAsync, as suggested by Filip Skakun:
// Page1.xaml.cs
private async void Button_Click(object sender, RoutedEventArgs e)
{
bitmapImage1 = new BitmapImage();
bitmapImage2 = new BitmapImage();
// Load both images in parallel
var task1 = loadImageAsync(bitmapImage1, "image1.jpg");
var task2 = loadImageAsync(bitmapImage2, "image2.jpg");
await Task.WhenAll(task1, task2);
Frame.Navigate(typeof(Page2));
}
private Task loadImageAsync(BitmapImage bitmapImage, string path)
{
var filePath = ApplicationData.Current.LocalFolder.Path + "\\" + path;
var file = await StorageFile.GetFileFromPathAsync(filePath);
var stream = await file.OpenAsync(FileAccessMode.Read);
await bitmapImage.SetSourceAsync(stream);
}
// Page2.xaml.cs
this.image1.Source = bitmapImage1;
this.image2.Source = bitmapImage2;
WinRT XAML Toolkit's AlternativeFrame control and AlternativePage one have a ShouldWaitForImagesToLoad property that when you set to true and navigate to a page - will only display the page when the images have loaded. AlternativePage has a Preload() method you can also override to add more things to await before displaying the page. The way it is implemented though is the page is first added to an invisible container in the visual tree, so loading of those BitmapImages gets triggered.
Another option might be to use SetSourceAsync() which you can await and I think it starts loading before you use the BitmapImage in the UI, but then you need to download the file yourself.

WinRT - UI virtualization loading images async blocking UI Thread

I'm having problems with my image loading blocking the UI thread so my gridview is not responsive in my windows store app.
What I'm trying to do is for the images in gridview to have a binding to image property in my view model. The value of image property is set by an async method. When app starts it loads all objects, but not the actual image data. The image data is loaded when the UI virtualization kicks in and requestes the image data via the image property bound to the image control in xaml.
All this is done in an observablecollection.
Here is some code:
private ImageSource _image = null;
private String _imagePath = null;
public ImageSource Image
{
get
{
SetImageFromStorageFile().ContinueWith(OnAsyncFail, TaskContinuationOptions.OnlyOnFaulted);
return this._image;
}
}
private async Task SetImageFromStorageFile()
{
this.IsLoading = true;
if (this._image == null && this._imagePath != null)
{
this._image = await BitmapLoader.GetPreviewImageFromStorageFile(this.StorageFile); //getting the actual data here
this.OnPropertyChanged("Image");
}
this.IsLoading = false;
}
This is all working fine except that the UI becomes unresponsive when accessing the image data.
As you can see I'm calling an async method from a property, I'm just reusing the code that I call from other places. When called from other places I can use await and the UI is responsive. The problem is that when using the gridviews UI virtualization I don't know how to run this async method without blocking the UI since properties are not possible to run async (as far as I know).
So I just want the gridview to run this property (or method) async instead of sync, but don't know how to do it.
Please help :)
private ImageSource _image = null;
private String _imagePath = null;
public ImageSource Image
{
get
{
if (_image != null)
return _image;
if (_imagePath != null && !IsLoading)
SetImageFromStorageFile();
return null;
}
}
private async void SetImageFromStorageFile()
{
if (this.IsLoading || this._image != null || this._imagePath == null)
return;
this.IsLoading = true;
try
{
this._image = await BitmapLoader.GetPreviewImageFromStorageFile(this.StorageFile); //getting the actual data here
this.IsLoading = false;
this.OnPropertyChanged("Image");
}
catch
{
OnAsyncFail();
}
}
Whatever strategy you choose, you need to return something first and fill it later. This is a sample which has been tested in a vanilla WinRT page; you can duplicate it by dropping it onto a page with an Image named image and a TextBlock named status. This can go in OnNavigatedTo, or another appropriate place.
BitmapImage imgsrc = new BitmapImage();
Task.Run(async () =>
{
await Task.Delay(10000);
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
new Windows.UI.Core.DispatchedHandler(() =>
{
imgsrc.UriSource = new Uri("http://www.gratuit-en-ligne.com/telecharger-gratuit-en-ligne/telecharger-image-wallpaper-gratuit/image-wallpaper-animaux/img/images/image-wallpaper-animaux-autruche.jpg");
}));
});
image.Source = imgsrc;
status.Text = "Done";
The thing I am "returning" is a BitmapImage, which is a subclass of ImageSource, so it's close to what you will want to use. The Task.Delay() is just to introduce obvious lag between startup and image population, so that you can see that the status text is set well before the image is loaded.
To make this work with your sample, you will need to create (or access) an ImageSource in your property and return it immediately, without waiting for it to be filled. Then you start off a background task that assigns the actual source data.
The actual chain of causation could be different than what I show here. You could, for instance, access the ImageSources from a pre-created collection of them. This would allow you to start loading the images before the property is ever called. But it sounds like this will be a start to get you moving in the right direction.

MediaElement Source cannot be set after SaveState/LoadState

(Note: All code has been severely simplified.)
Problem
MediaElement source not being set after Suspend/Resume. The CurrentState quickly changes to "Closed" after the source is set.
I am handling the MediaFailed event — it doesn't fire. I am also handling the MediaOpened event, which doesn't fire either.
Details
I have the following method which updates the MediaElement's Source. It works really well as long as the app is not trying to resume after having been Suspended.
private async void UpdateMediaElementSource(object sender, EventArgs e)
{
var videoSource = this.DefaultViewModel.CurrentSource; // a string
var file = await StorageFile.GetFileFromPathAsync(videoSource);
var videoStream = await file.OpenAsync(FileAccessMode.Read);
this.videoMediaElement.SetSource(videoStream, file.ContentType);
// The above line works many times as long as the app is not trying to Resume.
}
When the app is Suspended it calls the SaveState method:
protected async override void SaveState(Dictionary<String, Object> pageState)
{
pageState["MediaElementSource"] = this.DefaultViewModel.CurrentSource;
// I also made the videoStream global so I can dispose it — but no dice.
this.videoStream.Dispose();
this.videoStream = null;
}
When the app Resumes, it calls the LoadState method:
protected async override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
string source = string.Empty;
if (pageState != null)
{
if (pageState.ContainsKey("MediaElementSource"))
{
source = (string)pageState["MediaElementSource"];
}
}
var document = PublicationService.GetDocument(this.currentDocumentIdNumber);
this.DefaultViewModel = new DocumentViewModel(document);
this.DefaultViewModel.CurrentMarkerSourceChanged += UpdateMediaElementSource;
if (!string.IsNullOrEmpty(source))
{
// This causes the UpdateMediaElementSource() method to run.
this.DefaultViewModel.CurrentSource = source;
}
}
I appreciate any help on this issue. Please let me know if you need more details.
So, it turns out that the mediaElement's Source was being set before it was added to the visual tree.
Usually, this is not an issue when doing this:
mediaElement.Source = whatever;
but it IS an issue when you do this:
mediaElement.SetSource(stream, MimeType);
Conclusion
Make sure that your MediaElement is part of the VisualTree when you call SetSource(...).
A simple way to get my above code to work is by adding a global bool that is set to true once the mediaElement.Loaded event has fired. Then, inside the code that calls SetSource(), wrap that in an if(_mediaElementLoaded) block.

Categories