Layout render error when using Tasks modifying controls

Viewed 33

Description

Hello,

Rendering of Layouts are not updated if they contains controls (ContentView) with some rendering Task !

  1. I created a simple ContentView which contains three Image (the FireControl in the screenshot).
  2. In the constructor, I use a simple async method, with Task.Factory.StartNew, which will play on the visibility of the Face images with a small Task.Delay between them.
  3. In the MainPage, I load this control in a StackLayout, using a button : "Load control".
  • It is impossible for me to add a second control (if I press again "Load control" )!
  • It's like if the rendering of the StackLayout in my MainPage was not updated ...
  • If I dont call my task method: no rendering problem!
  • If I dont touch controls in my task method: no rendering problem !
  • If I spam-click "Load control" and "Remove control", sometimes loaded controls appears...

Is there a way to tell my StackLayout to update its rendering after adding a control? Is there a better way than Task.Factory.StartNew to perform frame animation in a control, so as not to block the rendering? (In this example the desired animation has been simplified)

Steps to Reproduce

My Test solution : Test Solution

TestControl

    public class TestControl : ContentView
    {
        protected Image FaceNormal;
        protected Image FaceLoose;
    
        public TestControl()
        {
            var principalImage = new Image { Source = "fire_principal.png" }; // The body
            FaceNormal = new Image { Source = "fire_facenormal.png" }; // Opened eyes
            FaceLoose = new Image { Source = "fire_faceloose.png" }; // Closed eyes
            Content = new Grid { Children = { principalImage, FaceNormal, FaceLoose } }; // Loaded in the Content
           
           // /!\ Causes rendering errors in the StackLayout
            Task.Factory.StartNew(() => StartFaceAnimation());
        }
    
        public async Task StartFaceAnimation()
        {
            Dispatcher.Dispatch(() =>
            {
                FaceNormal.IsVisible = true;
                FaceLoose.IsVisible = false;
            });
            await Task.Delay(2000);
            Dispatcher.Dispatch(() =>
            {
                FaceNormal.IsVisible = false;
                FaceLoose.IsVisible = true;
            });
        }
    }

MainPage

    public class MainPage : ContentPage
    {
        public MainPage()
        {
            var verticalStackLayout = new VerticalStackLayout();
    
            var loadTestControlButton = new Button { Text = "Load control", Margin = 2 }; 
            loadTestControlButton.Clicked += (o, e) => verticalStackLayout.Children.Add(new TestControl()); 
            verticalStackLayout.Children.Add(loadTestControlButton);
            
            var removeTestControlButton = new Button { Text = "Remove control", Margin = 2 }; 
            removeTestControlButton.Clicked += (o, e) => verticalStackLayout.Children.Remove(verticalStackLayout.Children.Last()); 
            verticalStackLayout.Children.Add(removeTestControlButton);
    
            Content = verticalStackLayout;
        }
    }
1 Answers

The trick is to call Arrange after IsVisible, see the below modifications:

public async Task StartFaceAnimation()
    {
        Dispatcher.Dispatch(() =>
        {
            FaceNormal.IsVisible = true;
            FaceLoose.IsVisible = false;
            Arrange(new Rect());
        });
        await Task.Delay(2000);
        Dispatcher.Dispatch(() =>
        {
            FaceNormal.IsVisible = false;
            FaceLoose.IsVisible = true;
            Arrange(new Rect());
        });
    }

It redraws the controls!

Related