Mémoire non libérée sur un carrousel

Jayme65 Messages postés 60 Date d'inscription lundi 23 avril 2007 Statut Membre Dernière intervention 26 mars 2019 - 17 oct. 2016 à 18:17
Jayme65 Messages postés 60 Date d'inscription lundi 23 avril 2007 Statut Membre Dernière intervention 26 mars 2019 - 18 oct. 2016 à 10:26
Bonjour,

J'ai un "carrousel" constitué de ContentControls avec une animation des éléments dans le déplacement.

Lorsque l'on descend (Flèche 'Bas' sur le clavier), un nouveau ContentControl (caché hors de la grille dans un premier temps) est créé en bas et ensuite animé..et à l'inverse, au dessus, celui qui est alors masqué est supprimé!

Avec la flèche gauche/droite du clavier on remonte/descend d'une page complète!

En inspectant l'utilisation de la mémoire je me suis rendu compte que la mémoire n'est pas libérée lorsque je descend 1 par 1.
Les 'anciens' ContentControl sont bien effacés au fur et à mesure de la grille (et d'un dictionnaire qui contient les éléments disponibles)..mais la mémoire n'est pas libérée.

Elle l'est par contre quand on remonte/descend d'une page (il faut dire qu'il y a alors un reset des enfants de la grille et du dictionnaire)
Imports System.IO
Imports System.Windows.Media.Animation

Class MainWindow
    Dim personDB As New List(Of Person)
    Dim listDictionary As Dictionary(Of Integer, ContentControl)
    Dim masterIndex As Integer = 0
    Dim delay As Double = 0.2
    ' ***** List settings *****
    Dim ListItemsNumber As Integer = 4      ' Number of items up and down the focused item
    Dim FocusedItemCoeff As Double = 2.5    ' Scale ration for focused item
    Dim ItemHeight As Integer               ' Height for a non-focused item
    Dim ItemCoordinates(ListItemsNumber * 2 + 3) As Integer
    '
    Private Sub MainWindow_Loaded(sender As Object, e As System.Windows.RoutedEventArgs) Handles Me.Loaded
        SetCoordinates()
        SetPersons()
        CreateList(masterIndex)
    End Sub
    Sub SetCoordinates()
        ItemHeight = Math.Floor(ListGrid.ActualHeight / (ListItemsNumber * 2 + FocusedItemCoeff))
        ' Top hidden Item
        ItemCoordinates(0) = ItemHeight * -1
        ' Top Items
        For up As Integer = 1 To ListItemsNumber
            ItemCoordinates(up) = ItemHeight * (up - 1)
        Next
        ' Center Item
        ItemCoordinates(ListItemsNumber + 1) = ItemHeight * ListItemsNumber
        ' Bottom items
        For down As Integer = 1 To ListItemsNumber
            ItemCoordinates(ListItemsNumber + 1 + down) = Math.Floor(ItemHeight * ((down - 1) + FocusedItemCoeff + ListItemsNumber))
        Next
        ' Bottom hidden Item
        ItemCoordinates(ListItemsNumber * 2 + 2) = Math.Floor(ItemHeight * (ListItemsNumber * 2 + FocusedItemCoeff))
    End Sub
    Sub SetPersons()
        For i As Integer = 1 To 200
            personDB.Add(New Person() With {.lastName = "LastName" & i.ToString, .imageUrl = "01.jpg"})
        Next
    End Sub
    Sub CreateList(focusedIndex As Integer)
        ' Clear grid and dictionary
        ListGrid.Children.Clear()
        listDictionary = New Dictionary(Of Integer, ContentControl)
        ' Top Items
        For up As Integer = 1 To ListItemsNumber
            If (focusedIndex - up > -1) Then
                Dim cc As New ContentControl
                cc.ContentTemplate = DirectCast(Me.Resources("ccImage"), DataTemplate)
                cc.Height = ItemHeight
                cc.Width = 200
                cc.VerticalAlignment = Windows.VerticalAlignment.Top
                cc.Margin = New Thickness(0, ItemCoordinates(ListItemsNumber + 1 - up), 0, 0)
                cc.Content = New Displayed With {.lastName = personDB(focusedIndex - up).lastName, .imageUrl = personDB(focusedIndex - up).imageUrl}
                listDictionary.Add(focusedIndex - up, cc)
                ListGrid.Children.Add(cc) 
            End If
        Next
        ' Center Item
        If personDB.Count >= 0 Then
            Dim cc As New ContentControl
            cc.ContentTemplate = DirectCast(Me.Resources("ccImage"), DataTemplate)
            cc.Height = Math.Floor(ItemHeight * FocusedItemCoeff)
            cc.Width = 300
            cc.VerticalAlignment = Windows.VerticalAlignment.Top
            cc.Margin = New Thickness(0, ItemCoordinates(ListItemsNumber + 1), 0, 0)
            Dim dp As Displayed = New Displayed With {.lastName = personDB(focusedIndex).lastName, .imageUrl = personDB(focusedIndex).imageUrl}
            cc.Content = dp
            listDictionary.Add(focusedIndex, cc)
            ListGrid.Children.Add(cc)
        End If
        ' Bottom Items
        For down As Integer = 1 To ListItemsNumber
            If (focusedIndex + down <= personDB.Count - 1) Then
                Dim cc As New ContentControl
                cc.ContentTemplate = DirectCast(Me.Resources("ccImage"), DataTemplate)
                cc.Height = ItemHeight
                cc.Width = 200
                cc.VerticalAlignment = Windows.VerticalAlignment.Top
                cc.Margin = New Thickness(0, ItemCoordinates(ListItemsNumber + 1 + down), 0, 0)
                Dim dp As Displayed = New Displayed With {.lastName = personDB(focusedIndex + down).lastName, .imageUrl = personDB(focusedIndex + down).imageUrl}
                cc.Content = dp
                listDictionary.Add(focusedIndex + down, cc)
                ListGrid.Children.Add(cc)
            End If
        Next
    End Sub
    Private Sub MainWindow_KeyDown(ByVal sender As Object, ByVal keyData As System.Windows.Input.KeyEventArgs) Handles Me.KeyDown
        If keyData.Key = Key.Down Then
            If masterIndex < personDB.Count - 1 Then
                GoDown()
            End If
        ElseIf keyData.Key = Key.Left Then
            If masterIndex > 0 Then
                If (masterIndex >= ListItemsNumber * 2 + 1) Then
                    masterIndex -= ListItemsNumber * 2 + 1
                Else
                    masterIndex = 0
                End If
                CreateList(masterIndex)
            End If
        ElseIf keyData.Key = Key.Right Then
            If masterIndex < personDB.Count - 1 Then
                If (masterIndex + (ListItemsNumber * 2 + 1) <= personDB.Count - 1) Then
                    masterIndex += ListItemsNumber * 2 + 1
                Else
                    masterIndex = personDB.Count - 1
                End If
                CreateList(masterIndex)
            End If
        End If
    End Sub
    Sub GoDown()
        ' Remove Top Hidden Item
        If listDictionary.ContainsKey(masterIndex - ListItemsNumber - 1) Then
            ListGrid.Children.Remove(listDictionary(masterIndex - ListItemsNumber - 1))
            listDictionary.Remove(masterIndex - ListItemsNumber - 1)
        End If
        ' Add new item at bottom
        If masterIndex + ListItemsNumber + 1 <= personDB.Count - 1 Then
            Dim cc As New ContentControl
            cc.ContentTemplate = DirectCast(Me.Resources("ccImage"), DataTemplate)
            cc.Height = ItemHeight
            cc.Width = 200
            cc.VerticalAlignment = Windows.VerticalAlignment.Top
            cc.Margin = New Thickness(0, ItemCoordinates(ListItemsNumber * 2 + 2), 0, 0)
            Dim dp As Displayed = New Displayed With {.lastName = personDB(masterIndex + ListItemsNumber + 1).lastName, .imageUrl = personDB(masterIndex + ListItemsNumber + 1).imageUrl}
            cc.Content = dp
            listDictionary.Add(masterIndex + ListItemsNumber + 1, cc)
            ListGrid.Children.Add(cc)
        End If
        '
        Dim myStoryboard As New Storyboard()
        '
        ' Top animation
        For up As Integer = 1 To ListItemsNumber
            If (masterIndex - up > -1) Then
                'Animating Margins
                Dim myDoubleAnimation As New ThicknessAnimation()
                Dim marginThickness As Thickness = listDictionary(masterIndex - up).Margin
                Dim NewmarginThickness As Thickness = New Thickness(0, ItemCoordinates(ListItemsNumber - up), 0, 0)
                myDoubleAnimation.From = marginThickness
                myDoubleAnimation.To = NewmarginThickness
                myDoubleAnimation.Duration = New Duration(TimeSpan.FromSeconds(delay))
                'myDoubleAnimation.EasingFunction = ease
                Storyboard.SetTarget(myDoubleAnimation, listDictionary(masterIndex - up))
                Storyboard.SetTargetProperty(myDoubleAnimation, New PropertyPath(Grid.MarginProperty))
                myStoryboard.Children.Add(myDoubleAnimation)
            End If
        Next

        ' Center animation
        If personDB.Count >= 0 Then
            If (masterIndex < personDB.Count - 1) Then
                'Animating Margins
                Dim myDoubleAnimation As New ThicknessAnimation()
                Dim marginThickness As Thickness = listDictionary(masterIndex).Margin
                Dim NewmarginThickness As Thickness = New Thickness(0, ItemCoordinates(ListItemsNumber), 0, 0)
                myDoubleAnimation.From = marginThickness
                myDoubleAnimation.To = NewmarginThickness
                myDoubleAnimation.Duration = New Duration(TimeSpan.FromSeconds(delay))
                Storyboard.SetTarget(myDoubleAnimation, listDictionary(masterIndex))
                Storyboard.SetTargetProperty(myDoubleAnimation, New PropertyPath(Grid.MarginProperty))
                myStoryboard.Children.Add(myDoubleAnimation)
                'Animating Height
                Dim myDoubleAnimation2 As New DoubleAnimation()
                Dim prevHeight As Integer = listDictionary(masterIndex).Height
                Dim newHeight As Integer = ItemHeight
                myDoubleAnimation2.From = prevHeight
                myDoubleAnimation2.To = newHeight
                myDoubleAnimation2.Duration = New Duration(TimeSpan.FromSeconds(delay))
                Storyboard.SetTarget(myDoubleAnimation2, listDictionary(masterIndex))
                Storyboard.SetTargetProperty(myDoubleAnimation2, New PropertyPath(Grid.HeightProperty))
                myStoryboard.Children.Add(myDoubleAnimation2)
                'Animating Width
                Dim myDoubleAnimation3 As New DoubleAnimation()
                Dim prevWidth As Integer = listDictionary(masterIndex).Width
                Dim newWidth As Integer = 200
                myDoubleAnimation3.From = prevWidth
                myDoubleAnimation3.To = newWidth
                myDoubleAnimation3.Duration = New Duration(TimeSpan.FromSeconds(delay))
                Storyboard.SetTarget(myDoubleAnimation3, listDictionary(masterIndex))
                Storyboard.SetTargetProperty(myDoubleAnimation3, New PropertyPath(Grid.WidthProperty))
                myStoryboard.Children.Add(myDoubleAnimation3)
            End If
        End If
        ' Bottom animation
        If (masterIndex + 1 <= personDB.Count - 1) Then
            'Animating Height
            Dim myDoubleAnimation2 As New DoubleAnimation()
            Dim prevHeight As Integer = listDictionary(masterIndex + 1).Height
            Dim newHeight As Integer = Math.Floor(ItemHeight * FocusedItemCoeff)
            myDoubleAnimation2.From = prevHeight
            myDoubleAnimation2.To = newHeight
            myDoubleAnimation2.Duration = New Duration(TimeSpan.FromSeconds(delay))
            Storyboard.SetTarget(myDoubleAnimation2, listDictionary(masterIndex + 1))
            Storyboard.SetTargetProperty(myDoubleAnimation2, New PropertyPath(Grid.HeightProperty))
            myStoryboard.Children.Add(myDoubleAnimation2)
            'Animating Width
            Dim myDoubleAnimation3 As New DoubleAnimation()
            Dim prevWidth As Integer = listDictionary(masterIndex + 1).Width
            Dim newWidth As Integer = 300
            myDoubleAnimation3.From = prevWidth
            myDoubleAnimation3.To = newWidth
            myDoubleAnimation3.Duration = New Duration(TimeSpan.FromSeconds(delay))
            Storyboard.SetTarget(myDoubleAnimation3, listDictionary(masterIndex + 1))
            Storyboard.SetTargetProperty(myDoubleAnimation3, New PropertyPath(Grid.WidthProperty))
            myStoryboard.Children.Add(myDoubleAnimation3)
        End If
        For down As Integer = 1 To ListItemsNumber + 1
            If (masterIndex + down <= personDB.Count - 1) Then
                'Animating Margins
                Dim myDoubleAnimation As New ThicknessAnimation()
                Dim marginThickness As Thickness = listDictionary(masterIndex + down).Margin
                Dim NewmarginThickness As Thickness = New Thickness(0, ItemCoordinates(ListItemsNumber + down), 0, 0)
                myDoubleAnimation.From = marginThickness
                myDoubleAnimation.To = NewmarginThickness
                myDoubleAnimation.Duration = New Duration(TimeSpan.FromSeconds(delay))
                Storyboard.SetTarget(myDoubleAnimation, listDictionary(masterIndex + down))
                Storyboard.SetTargetProperty(myDoubleAnimation, New PropertyPath(Grid.MarginProperty))
                myStoryboard.Children.Add(myDoubleAnimation)
            End If
        Next
        myStoryboard.Begin()
        '
        masterIndex += 1
    End Sub
End Class

Public Class Person
    Public Shared ReadOnly DescriptComparer As New DescriptComparerClass
    Public lastName As String
    Public imageUrl As String
    Sub New()
    End Sub
    Public Class DescriptComparerClass
        Implements IComparer(Of Person)
        Public Function Compare(ByVal x As Person, ByVal y As Person) As Integer Implements System.Collections.Generic.IComparer(Of Person).Compare
            Return String.Compare(x.lastName, y.lastName, True)
        End Function
    End Class
End Class

Public Class Displayed
    Public _lastname As String
    Public _imageUrl As String
    Public Property lastName() As String
        Get
            Return _lastname
        End Get
        Set(value As String)
            _lastname = value
        End Set
    End Property
    Public Property imageUrl() As String
        Get
            Return _imageUrl
        End Get
        Set(value As String)
            _imageUrl = value
        End Set
    End Property
    Sub New()
    End Sub
End Class

Public Class ImagePathConverter
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.Convert
        Dim relative As [String] = TryCast(value, String)
        If String.IsNullOrEmpty(relative) Then
            Return Nothing
        End If
        Return System.AppDomain.CurrentDomain.BaseDirectory & "Images\" & relative
    End Function
    Public Function ConvertBack(ByVal value As Object, ByVal targetType As Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
        Throw New NotImplementedException()
    End Function
End Class

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"    
    Title="MainWindow" Height="700" Width="800">
    <Window.Resources>
        <local:ImagePathConverter x:Key="imgPathConverter" />
        <DataTemplate x:Key="ccImage" >
            <Border Padding="4">
                <Image>
                    <Image.Source>
                        <BitmapImage UriSource="{Binding imageUrl, Converter={StaticResource imgPathConverter}}" CacheOption="OnLoad" CreateOptions="IgnoreImageCache" RenderOptions.BitmapScalingMode="LowQuality"/>
                    </Image.Source>
                </Image>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid Name="ListGrid" Width="400" VerticalAlignment="Top" Height="600" HorizontalAlignment="Left" Background="Black"></Grid>
    </Grid>
</Window>

Le problème intervient donc dans cette partie
Sub GoDown()
        ' Remove Top Hidden Item
        If listDictionary.ContainsKey(masterIndex - ListItemsNumber - 1) Then
            ListGrid.Children.Remove(listDictionary(masterIndex - ListItemsNumber - 1))
            listDictionary.Remove(masterIndex - ListItemsNumber - 1)
        End If

...le ContentControl enlevé de la grille semble toujours être existant "quelque part" ;-) car la mémoire grossit de plus en plus lors du défilement vers le bas (sur l'exemple joint, j'arrive à 500Mo sur une liste de 200 objets)

J'espère que je suis assez clair dans mes explications et que vous voudrez bien m'aider! Merci
NB: pour que le code marche, il faut créer un dossier 'Images' avec un fichier '01.jpg' à l'intérieur, au niveau de l'application.
On se déplace avec les flèches 'bas, gauche, droite'

4 réponses

Whismeril Messages postés 19027 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 24 avril 2024 656
18 oct. 2016 à 09:40
Bonjour, il faut prendre en compte la façon dont c'est placé en mémoire.

Une collection (tableau, liste, dictionnaire etc..) c'est un espace où tu vas stocker les adresses de tes objets. Quand tu ajoutes un item, si cet item n'existe pas et que tu fais un Add de New toto, dans la mémoire il se passe deux choses différentes, d'abord ça crée un nouvel item quelque par, puis ça mets l'adresse de ce nouvel item dans la collection. Quand tu retires un item de la collection, ça e fait que retirer l'adresse de la collection.
Ça n'est pas parce que je retire un objet d'une collection qu'il n'est pas encore utiliser par ailleurs.

Ensuite intervient le garbage collector, son rôle est de nettoyer la mémoire inutile, la première chose à savoir est qu'il intervient quand il veut, même quand tu lui dit expressément qu'il faut y penser. Ensuite il commence par nettoyer les espaces qui sont clairement laisser à l'abandon, c'est à dire que tu as écrit dans le code que tu les "disposes" (voir l'interface IDisposable), après seulement il cherche les objets orphelins, c'est à dire ceux qui ne sont pas disposés mais dont il ne subsiste pas d'adresse stocké quelque part.

Donc si tu charges trop la ram, pour forcer un peu les choses, tu disposes tes objets (tu implémentés idisposable si nécessaire) et tu "forces" le garbage collector. Le Net est plein d'exemples pour les 2.

Cherches aussi en c#, et traduis le code en ligne, y a plusieurs sites dédiés
0
Jayme65 Messages postés 60 Date d'inscription lundi 23 avril 2007 Statut Membre Dernière intervention 26 mars 2019 2
18 oct. 2016 à 09:50
Merci pour ta réponse globale! Je n'ai pas ces problèmes exposés!

Je pense maintenant que le problème doit être lié au Binding dans le XAML..car si je compose les ContentsControls par le code le problème de mémoire disparait

Sub GoDown()
        ...
        ' Add new item at bottom
        If masterIndex + ListItemsNumber + 1 <= personDB.Count - 1 Then
            Dim img As New Image
            Dim bmp As New BitmapImage()
            bmp = New BitmapImage(New Uri(System.AppDomain.CurrentDomain.BaseDirectory & "Images\" & personDB(masterIndex + ListItemsNumber + 1).imageUrl))
            img.Source = bmp
            Dim cc As New ContentControl
            cc.Height = ItemHeight
            cc.Width = 200
            cc.VerticalAlignment = Windows.VerticalAlignment.Top
            cc.Margin = New Thickness(0, ItemCoordinates(ListItemsNumber * 2 + 2), 0, 0)
            cc.Content = img
            listDictionary.Add(masterIndex + ListItemsNumber + 1, cc)
            ListGrid.Children.Add(cc)
        End If


SI le code est adapté de cette façon pour ne plus aller chercher son ContentTemplate dans le XAML...plus de problèmes avec la mémoire!
Avez-vous une idéé de pourquoi cela arrive si j'utilise plutôt la version XAML de la chose?
Merci!!
0
Whismeril Messages postés 19027 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 24 avril 2024 656
18 oct. 2016 à 10:21
Oui, comme tu ne disposes pas tes objets, le binding les reconnais comme existants.
0
Jayme65 Messages postés 60 Date d'inscription lundi 23 avril 2007 Statut Membre Dernière intervention 26 mars 2019 2
18 oct. 2016 à 10:26
D'accord, merci pour ta réponse! Je vais voir dans cette direction et je reviens en rendre compte ensuite!
Merci!
0
Rejoignez-nous