0

I need to set images to get from a rest service inside every viewer of a ListView. These images change periodically to create a "gif" effect and, after a period, I recall the service to get the updated images about the webcam. The problem is that not all images are set but just a part of them and sometimes no one of them is set.

My code is the following:

 public class WebcamListViewModel : BaseViewModel
{
    public ICommand InitializeWebcamsCommand { set; get; }
    public ICommand OpenVideoWebcamCommand { set; get; }

    private List<Webcam> _ListOfWebcam { get; set; }
    public List<Webcam> ListOfWebcam
    {
        get { return _ListOfWebcam; }
        set
        {
            _ListOfWebcam = value;
            OnPropertyChanged();
        }
    }

    private IFolder folder;
    private int _Counter { get; set; }
    public int Counter
    {
        get { return _Counter; }
        set
        {
            _Counter = value;
            OnPropertyChanged();
        }
    }

    private Task SetFrameOnViewTask;

    private Task DownloadFramesTask;

    CancellationTokenSource tokenSourceSetFrame = new CancellationTokenSource();

    CancellationTokenSource tokenSourceDownloadFrames = new CancellationTokenSource();

    CancellationToken cancellationTokenSetFrame;

    CancellationToken cancellationTokenDownloadFrames;

    public WebcamListViewModel(INavigationService navigationService, IApiAutostradeManagerFactory apiAutostradeManagerFactory) : base(navigationService,apiAutostradeManagerFactory)
    {

        OpenVideoWebcamCommand = new Command<Webcam>(async (webcam) => {
            await navigationService.NavigateAsync(Locator.WebcamVideoPopUpPage);
            Messenger.Default.Send(new InfoWebcamVideoMessage(webcam.c_mpr, webcam.c_uuid, webcam.t_str_vid));
        });

        InitializeWebcamsCommand = new Command(async () => await RunSafe(InitializeWebcams()));
        InitializeWebcamsCommand.Execute(null);

        cancellationTokenDownloadFrames = tokenSourceDownloadFrames.Token;

        DownloadFramesTask = new Task(async () => {
            cancellationTokenDownloadFrames.ThrowIfCancellationRequested();

            while (true)
            {
                try
                {
                    await DownloadAndSetWebcamImages();
                    await Task.Delay(2000);

                    if (cancellationTokenDownloadFrames.IsCancellationRequested)
                    {

                        // Clean up here, then...
                        cancellationTokenDownloadFrames.ThrowIfCancellationRequested();
                    }
                }
                catch (System.FormatException e)
                {
                    Console.WriteLine(e.Message);
                }
            }
        }, cancellationTokenDownloadFrames);

        SetFrameOnViewTask = new Task(async () =>
        {
            cancellationTokenSetFrame.ThrowIfCancellationRequested();

            while (true)
            {
                try
                {
                    Counter++;
                    await Task.Delay(500);

                    if (cancellationTokenSetFrame.IsCancellationRequested)
                    {
                        Counter = 0;
                        // Clean up here, then...
                        cancellationTokenSetFrame.ThrowIfCancellationRequested();
                    }
                }
                catch (FormatException e)
                {
                    Console.WriteLine(e.Message);
                }
            }
        }, cancellationTokenSetFrame);
    }

    private async Task InitializeWebcams()
    {
        folder = await FileSystem.Current.LocalStorage.GetFolderAsync("WebcamImages");
        ListOfWebcam = await RepositoryHelper.Instance.WebcamRepository.GetItemsAsync();
        ListOfWebcam = ListOfWebcam.OrderByDescending(x => x.n_prg_km).ToList();
        try
        {

            if (DownloadFramesTask.Status == TaskStatus.Running)
            {
                try
                {
                    tokenSourceDownloadFrames.Cancel();
                }
                finally
                {
                    tokenSourceDownloadFrames.Dispose();
                }
            }

            DownloadFramesTask.Start();

            if (SetFrameOnViewTask.Status == TaskStatus.Running)
            {
                try
                {
                    tokenSourceSetFrame.Cancel();
                }
                finally
                {
                    tokenSourceSetFrame.Dispose();
                }
            }

            SetFrameOnViewTask.Start();
        }
        catch (System.InvalidOperationException)
        {}
    }

    private async Task DownloadAndSetWebcamImages()
    {

        await ImageService.Instance.InvalidateCacheAsync(CacheType.All);
        foreach (var web in ListOfWebcam)
        {
            web.image1 = await GetWebcamFrame(web.frame1);
            web.image2 = await GetWebcamFrame(web.frame2);
            web.image3 = await GetWebcamFrame(web.frame3);
            web.image4 = await GetWebcamFrame(web.frame4);
        }
    }

    private async Task<ImageSource> GetWebcamFrame(string urlFrame)
    {
        try
        {
            var frameResponse = await ApiManager.GetWebcamFrame(urlFrame);
            var base64Image = await frameResponse.Content.ReadAsStringAsync();

            byte[] imageData = Convert.FromBase64String(base64Image);
            return (ImageSource.FromStream(() => { return new MemoryStream(imageData); }));
        }
        catch (FormatException e)
        {
            throw e;
        }
    }

In my viewModel, I got two tasks: DownloadFramesTask and SetFrameOnViewTask, that every 500 ms increment a counter, that is used to show one of the four frames at the turn.

<ListView ItemsSource="{Binding ListOfWebcam}"
              SeparatorVisibility="None"
              CachingStrategy="RetainElement"
              RowHeight="250"
              VerticalOptions="FillAndExpand"
              x:Name="ListWebcam">

        <ListView.Header>
            <StackLayout x:Name="HeaderStackLayout"
                               Padding="5,25,0,30"
                               Orientation="Horizontal"
                               HorizontalOptions="FillAndExpand">

                    <Label  x:Name="LabelHeader"
                              Text="Webcam:"
                              FontSize="Large"
                              FontAttributes="Bold"
                              TextColor="{x:Static statics:Palette.PrimaryColor}"
                              VerticalOptions="Center"
                              HorizontalOptions="Start" Margin="10,0,0,0"/>
            </StackLayout>
        </ListView.Header>

        <ListView.ItemTemplate>
            <DataTemplate>

                <controls:ExtendedViewCell SelectedItemBackgroundColor="#fafafa">

                    <Grid x:Name="GridWebcam">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>

                        <Frame Grid.Column="1"
                                   Grid.RowSpan="2"
                                   CornerRadius="20"
                                   BackgroundColor="{x:Static statics:Palette.PrimaryColor}"
                                   VerticalOptions="FillAndExpand"
                                   HorizontalOptions="FillAndExpand"
                                   HasShadow="True"
                                   Margin="5,10">

                            <StackLayout>

                                <Label Text="{Binding t_str_vid,Converter={StaticResource WebcamNameConverter}}"
                                           FontSize="Medium"
                                           TextColor="White"
                                           FontAttributes="Bold"
                                           HorizontalOptions="FillAndExpand"
                                           VerticalOptions="FillAndExpand">

                                </Label>

                                <Label TextColor="White"
                                           FontSize="Medium"
                                           Text="{Binding direzione,Converter={StaticResource DirectionToStringConverter}}"/>

                                <StackLayout Orientation="Horizontal">
                                    <ffimageloading:CachedImage DownsampleToViewSize="True"
                                                                VerticalOptions="FillAndExpand"
                                                                HorizontalOptions="StartAndExpand"
                                                                IsVisible="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame1Converter}}"
                                                                IsEnabled="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame1Converter}}"
                                                                Source="{Binding image1}"/>
                                    <ffimageloading:CachedImage x:Name="SecondFrame"
                                                                DownsampleToViewSize="True"
                                                                Grid.Row="1"
                                                                Grid.Column="0"
                                                                IsVisible="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame2Converter}}"
                                                                IsEnabled="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame2Converter}}"

                                                                VerticalOptions="FillAndExpand"
                                                                HorizontalOptions="StartAndExpand"
                                                                Source="{Binding image2}"/>

                                    <ffimageloading:CachedImage x:Name="ThirdFrame"
                                                                Grid.Row="1"
                                                                Grid.Column="0"

                                                                IsVisible="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame3Converter}}"
                                                                IsEnabled="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame3Converter}}"

                                                                VerticalOptions="FillAndExpand"
                                                                HorizontalOptions="StartAndExpand"
                                                                Source="{Binding image3}"/>

                                    <ffimageloading:CachedImage x:Name="FourthFrame"
                                                                Grid.Row="1"
                                                                Grid.Column="0"
                                                                IsVisible="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame4Converter}}"
                                                                IsEnabled="{Binding Source={x:Reference WebcamList},Path=BindingContext.Counter,Converter={StaticResource VisibleFrame4Converter}}"

                                                                VerticalOptions="FillAndExpand"
                                                                HorizontalOptions="StartAndExpand"
                                                                Source="{Binding image4}"/>

                                    <iconize:IconButton Text="fas-play-circle"
                                                            FontSize="50"
                                                            HorizontalOptions="EndAndExpand"
                                                            VerticalOptions="EndAndExpand"
                                                            TextColor="White"
                                                            Command="{Binding BindingContext.OpenVideoWebcamCommand, Source={x:Reference ListWebcam}}"
                                                            CommandParameter="{Binding}"
                                                            BackgroundColor="Transparent"/>
                                </StackLayout>
                            </StackLayout>
                        </Frame>
                    </Grid>
                </controls:ExtendedViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

In My dataTemplate I bind isVisible and isEnabled of every image with the counter, that is converted into a boolean thanks to the four converters. I'll show just one of them:

public class VisibleFrame1Converter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if ((int)value % 4 == 0)
            return true;
        else
            return false;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

This one, for example, is used to show the first frame when the condition about counter is satisfied. My model class is the following:

public class Webcam : INotifyPropertyChanged
{
    [PrimaryKey, AutoIncrement]
    public int idWebcam { get; set; }
    public string c_mpr { get; set; }
    public int c_tel { get; set; }
    public string c_uuid { get; set; }
    public string direzione { get; set; }
    public string frame1 { get; set; }
    public string frame2 { get; set; }
    public string frame3 { get; set; }
    public string frame4 { get; set; }

    public double n_crd_lat { get; set; }
    public double n_crd_lon { get; set; }
    public int n_ind_pri { get; set; }
    public double n_prg_km { get; set; }
    public int ramo { get; set; }
    public int str { get; set; }
    public string strada { get; set; }
    public string t_str_vid { get; set; }
    public string thumb { get; set; }

    public ImageSource image1 { get; set; }

    public ImageSource image2 { get; set; }

    public ImageSource image3 { get; set; }

    public ImageSource image4 { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

The expected result is to show every frame downloaded (I'm sure that every frame is downloaded, I checked one by one) in the view and download the updated version every tot time.

Giuseppe Pennisi
  • 396
  • 3
  • 22

1 Answers1

1

Maybe your problem is Webcam.cs , also need to use INotifyPropertyChanged for its property. As follow:

public class Webcam : INotifyPropertyChanged
{
    [PrimaryKey, AutoIncrement]
    public int idWebcam { get; set; }
    public string c_mpr { get; set; }
    public int c_tel { get; set; }
    public string c_uuid { get; set; }
    public string direzione { get; set; }
    public string frame1 { get; set; }
    public string frame2 { get; set; }
    public string frame3 { get; set; }
    public string frame4 { get; set; }

    public double n_crd_lat { get; set; }
    public double n_crd_lon { get; set; }
    public int n_ind_pri { get; set; }
    public double n_prg_km { get; set; }
    public int ramo { get; set; }
    public int str { get; set; }
    public string strada { get; set; }
    public string t_str_vid { get; set; }
    public string thumb { get; set; }

    // modified code
    ImageSource image1 ;

    public ImageSource Image1
        {
            set
            {
                if (image1 != value)
                {
                    image1 = value;
                    OnPropertyChanged("Image1");
                }
            }
            get
            {
                return image1 ;
            }
        }

    ImageSource image2 ;

    public ImageSource Image2
        {
            set
            {
                if (image2 != value)
                {
                    image2 = value;
                    OnPropertyChanged("Image2");
                }
            }
            get
            {
                return image2 ;
            }
        }

    ImageSource image3 ;

    public ImageSource Image3
        {
            set
            {
                if (image3 != value)
                {
                    image3 = value;
                    OnPropertyChanged("Image3");
                }
            }
            get
            {
                return image3 ;
            }
        }

    ImageSource image4 ;

    public ImageSource Image4
        {
            set
            {
                if (image4 != value)
                {
                    image4 = value;
                    OnPropertyChanged("Image4");
                }
            }
            get
            {
                return image4 ;
            }
        }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Any other you want updated property ,all need to use OnPropertyChanged in Model.Just in WebcamListViewModel.cs using , Webcam's property can not work.

Junior Jiang
  • 12,430
  • 1
  • 10
  • 30
  • Thanks, it helps a bit. Now image sources are loaded but just a part of them are loaded immediately, the last ten elements of the list got their images displayed after some time. – Giuseppe Pennisi Aug 09 '19 at 12:24
  • @GiuseppePennisi Okey , I think not all displaying immediately is a normal phenomenon.Because images loaded need time ,and if size of images is large there will need more time to load them. About the loading delay of the image, this need to optimize.You can create new question for it. – Junior Jiang Aug 12 '19 at 01:32
  • Thanks, for the help. I opened another question about code optimization. Here the link: https://stackoverflow.com/questions/57559144/how-to-optimize-the-loading-of-a-big-amout-of-images – Giuseppe Pennisi Aug 19 '19 at 14:50
  • @GiuseppePennisi Okey , I will check it. – Junior Jiang Aug 20 '19 at 01:08
  • @GiuseppePennisi For large images, the image can be compressed and displayed without affecting the clarity of the picture. You can also use custom renderer to load images on native Android and iOS methods, which will improve the display. – Junior Jiang Aug 20 '19 at 03:00