Bonjour la communauté,

Je me décide enfin à écrire au sujet d'un problème récurent.

Quand je fais RowDetailsTemplate avec un UserControl, le Datacontext n'arrête pas de se modifié une fois qu'on a affiché au moins une fois le Template et qu'on scroll dans le dataGrid alors que plus aucun détail n'est affiché et cela me pose des problèmes de performance car il n'arrête pas de monter des propriétés qui ne devraient être accédée que quand on clic sur une ligne pour voir le Template.


J'ai fait un petit projet pour vous montrer le soucis.

Soit le MainWindow avec la grille principale :
Code xaml : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<Window x:Class="MainWindow"  Name="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        d:DataContext="{d:DesignInstance Type=local:MainWindow}"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <DataGrid ItemsSource="{Binding Employees}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" Width="*"/>
                <DataGridTextColumn Header="Id" Binding="{Binding Id}" IsReadOnly="True" Width="*"/>
             </DataGrid.Columns>
            <DataGrid.RowDetailsTemplate >
                <DataTemplate>
                    <local:ucDetail Width="Auto" Height="Auto" Padding="30,0,0,0"/>
 
                </DataTemplate>
            </DataGrid.RowDetailsTemplate>
        </DataGrid>
    </Grid>
</Window>

Le code behind de ce MainWindow et de la class Employee qui sert d’élément à afficher dans la grille :
Code vb.Net : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
 
Class MainWindow
    Implements INotifyPropertyChanged
 
#Region "INotifyPropertyChanged"
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
 
    Friend Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
#End Region
 
    Private _Employees As List(Of Employee)
    Public Property Employees() As List(Of Employee)
        Get
            Return _Employees
        End Get
        Set(ByVal value As List(Of Employee))
            _Employees = value
            NotifyPropertyChanged()
        End Set
    End Property
 
    Sub New()
 
        ' This call is required by the designer.
        InitializeComponent()
 
        ' Add any initialization after the InitializeComponent() call.
        Dim l As New List(Of Employee)
        For i As Integer = 1 To 1000
            l.Add(New Employee With {.Id = i, .Name = "Test" & i})
        Next
        Employees = l
    End Sub
End Class
 
Public Class Employee
    Private _Name As String
    Public Property Name() As String
        Get
            Trace.WriteLine("Get name " & _Name)
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property
    Private _Id As Integer
    Public Property Id() As Integer
        Get
            Trace.WriteLine("Get Id " & _Id)
            Return _Id
        End Get
        Set(ByVal value As Integer)
            _Id = value
        End Set
    End Property
 
    ReadOnly Property NameAndId As String
        Get
            Trace.WriteLine("Get NameAndId " & _Name & " and " & _Id)
            Return _Name & " and " & _Id
        End Get
    End Property
End Class

Le code de l'user control détail :
Code xaml : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<UserControl x:Class="ucDetail"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800" DataContextChanged="UserControl_DataContextChanged">
    <Grid>
        <StackPanel>
            <TextBox Text="{Binding Name}"/>
            <TextBox Text="{Binding Id}"/>
            <TextBox Text="{Binding NameAndId, Mode=OneWay}"/>
        </StackPanel>
    </Grid>
</UserControl>

Avec son code behind qui n'a que l’événement DataContextChanged pour l'afficher dans la fenêtre de sortie et visualiser le comportement.
Code vb.Net : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
Public Class ucDetail
    Private Sub UserControl_DataContextChanged(sender As Object, e As DependencyPropertyChangedEventArgs)
        If e.OldValue Is Nothing Then
            Trace.WriteLine("DataContextChanged : New value = " & CType(e.NewValue, Employee).Id)
        Else
            Trace.WriteLine("DataContextChanged : Old value = " & CType(e.OldValue, Employee).Id & " => New value = " & CType(e.NewValue, Employee).Id)
        End If
    End Sub
End Class



TESTS

Au lancement de l'application, j'ai bien le DataGrid qui s'affiche avec les deux colonnes.
Dans la fenêtre de sortie on peut voir qu'il n'accède bien qu'aux propriétés affichées dans la grille :
Code Immediate Window : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
... 
Get name Test1
Get Id 1
Get name Test2
Get Id 2
Get name Test3
Get Id 3
Get name Test4
Get Id 4
Get name Test5
Get Id 5
Get name Test6
Get Id 6
Get name Test7
Get Id 7
Get name Test8
Get Id 8
Get name Test9
Get Id 9
Get name Test10
Get Id 10
Get name Test11
Get Id 11
Get name Test12
Get Id 12
Get name Test13
Get Id 13
Get name Test14
Get Id 14
Get name Test15
Get Id 15
Get name Test16
Get Id 16
Get name Test17
Get Id 17
Get name Test18
Get Id 18
Get name Test19
Get Id 19
Get name Test20
Get Id 20
Get name Test21
Get Id 21
Get name Test22
Get Id 22

(Je l'ai mis dans les balises code car ça condense mieux la lecture)

Dès qu'on clic sur une ligne, le template s'ouvre et affiche bien son détail, ça se voit dans la fenêtre de sortie ou ces lignes apparaissent :
Code Immediate Window : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
... 
DataContextChanged : New value = 4
Get name Test4
Get Id 4
Get NameAndId Test4 and 4

Donc il accède bien à la propriété NameAndId, le comportement est tout à fait normal.

On continue de scoller de haut en bas en réaffichant la ligne ouverte, pas de soucis on a bien l'accès aux propriétés affichées.

Les choses se corses après avoir ouvert plusieurs lignes, donc je clic une fois sur l'employé 6 puis sur le 10 et enfin sur le 15 :
Code Immediate Window : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
... 
DataContextChanged : New value = 6
Get name Test6
Get Id 6
Get NameAndId Test6 and 6
Get Id 10
DataContextChanged : New value = 10
Get name Test10
Get Id 10
Get NameAndId Test10 and 10
Get Id 15
DataContextChanged : New value = 15
Get name Test15
Get Id 15
Get NameAndId Test15 and 15

Jusqu'ici tout va bien.

Maintenant vient le problème. Essayons simplement de scroller dans la grille :
Code Immediate Window : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
... 
Get name Test20
Get Id 20
Get name Test21
Get Id 21
Get name Test3
Get Id 3
Get name Test22
Get Id 22
Get name Test23
Get Id 23
Get name Test24
Get Id 24
Get name Test25
Get Id 25
Get name Test26
Get Id 26
Get name Test27
Get Id 27
Get name Test28
Get Id 28
Get Id 4
Get Id 28
DataContextChanged : Old value = 4 => New value = 28
Get name Test28
Get Id 28
Get NameAndId Test28 and 28
Get name Test29
Get Id 29
Get name Test30
Get Id 30
Get Id 6
Get Id 30
DataContextChanged : Old value = 6 => New value = 30
Get name Test30
Get Id 30
Get NameAndId Test30 and 30
Get name Test31
Get Id 31
Get name Test32
Get Id 32
Get name Test33
Get Id 33
Get name Test34
Get Id 34
Get Id 10
Get Id 34
DataContextChanged : Old value = 10 => New value = 34
Get name Test34
Get Id 34
Get NameAndId Test34 and 34
Get name Test35
Get Id 35
Get name Test36
Get Id 36
Get name Test37
Get Id 37
Get name Test38
Get Id 38
Get name Test39
Get Id 39
Get name Test40
Get Id 40
Get name Test41
Get Id 41
Get name Test42
Get Id 42
Get name Test43
Get Id 43
Get name Test44
Get Id 44
Get name Test45
Get Id 45
Get name Test46
Get Id 46
Get name Test47
Get Id 47
Get name Test48
Get Id 48
Get name Test49
Get Id 49
Get name Test50
Get Id 50
Get name Test51
Get Id 51
Get Id 28
Get Id 51
DataContextChanged : Old value = 28 => New value = 51
Get name Test51
Get Id 51
Get NameAndId Test51 and 51
Get name Test52
Get Id 52
Get name Test53
Get Id 53
Get Id 30
Get Id 53
DataContextChanged : Old value = 30 => New value = 53
Get name Test53
Get Id 53
Get NameAndId Test53 and 53
Get name Test54
Get Id 54
Get name Test55
Get Id 55
Get name Test56
Get Id 56
Get name Test57
Get Id 57
Get Id 34
Get Id 57
DataContextChanged : Old value = 34 => New value = 57
Get name Test57
Get Id 57
Get NameAndId Test57 and 57
Get name Test58
Get Id 58

Donc je n'ai fait que descendre dans la grille, on le voit d'ailleurs avec l'incrémentation des ID. Donc le dernier ucDetail affiché n'est plus visible au moment où en ligne 24 il me change le dataContext du premier élément que j'avais ouvert par un sur lequel je ne clic absolument pas. Il y a donc une réutilisation des templates créés avec un essais de prédiction d'un nouvel élément.

Dans le cas de cet exemple ça ne pose pas de soucis particulier mais dans le cas de mon travail, dans le détail j'affiche plusieurs choses dont un historique de l'employé et pour se faire il y des accès en base de donnée. Je vous raconte pas les lags lorsque l'on descend rapidement avec la barre de defilement et comment la BD se fait mitrailler pour rien.

Avez-vous déjà été confronté à ce problème? Auriez-vous une piste pour l'éliminer?

En vous remerciant d'avance.