Guide pratique pour s'assurer que la ligne sélectionnée dans une table enfant reste au bon emplacement

Souvent, lors de l’utilisation de la liaison de données dans des Windows Forms, vous affichez des données dans ce que l’on appelle une vue parent/enfant ou maître/détails. Cela fait référence à un scénario de liaison de données où les données de la même source sont affichées dans deux contrôles. La modification de la sélection dans un contrôle entraîne la modification des données affichées dans le deuxième contrôle. Par exemple, le premier contrôle peut contenir une liste de clients et le second une liste de commandes associées au client sélectionné dans le premier contrôle.

À compter de .NET Framework version 2.0, quand vous affichez des données dans une vue parent/enfant, vous devrez peut-être effectuer des étapes supplémentaires pour vous assurer que la ligne actuellement sélectionnée dans la table enfant n'est pas réinitialisée à la première ligne de la table. Pour cela, vous devez mettre en cache la position de la table enfant et la réinitialiser une fois que la table parente a changé. En général, la réinitialisation de l'enfant se produit la première fois qu'un champ sur une ligne de la table parente change.

Pour mettre en cache la position actuelle de l'enfant

  1. Déclarez une variable entière pour stocker la position de liste de l'enfant et une variable booléenne pour stocker une valeur indiquant s'il faut mettre en cache la position de l'enfant.

    private int cachedPosition = -1;
    private bool cacheChildPosition = true;
    
    Private cachedPosition As Integer = - 1
    Private cacheChildPosition As Boolean = True
    
    
  2. Gérez l’événement ListChanged pour le CurrencyManager de la liaison et vérifiez s’il y a un ListChangedType égal à Reset.

  3. Vérifiez la position actuelle du CurrencyManager. Si elle est supérieure à la première entrée de la liste (en général 0), enregistrez-la dans une variable.

    void relatedCM_ListChanged(object sender, ListChangedEventArgs e)
    {
        // Check to see if this is a caching situation.
        if (cacheChildPosition && cachePositionCheckBox.Checked)
        {
            // If so, check to see if it is a reset situation, and the current
            // position is greater than zero.
            CurrencyManager relatedCM = sender as CurrencyManager;
            if (e.ListChangedType == ListChangedType.Reset && relatedCM.Position > 0)
    
                // If so, cache the position of the child table.
                cachedPosition = relatedCM.Position;
        }
    }
    
    Private Sub relatedCM_ListChanged(ByVal sender As Object, _
        ByVal e As ListChangedEventArgs)
        ' Check to see if this is a caching situation.
        If cacheChildPosition AndAlso cachePositionCheckBox.Checked Then
            ' If so, check to see if it is a reset situation, and the current
            ' position is greater than zero.
            Dim relatedCM As CurrencyManager = sender
            If e.ListChangedType = ListChangedType.Reset _
                AndAlso relatedCM.Position > 0 Then
    
                ' If so, cache the position of the child table.
                cachedPosition = relatedCM.Position
            End If
        End If
    
    End Sub
    
  4. Gérez l'événement CurrentChanged de la liste parente pour le gestionnaire de devise parent. Dans le gestionnaire, définissez la valeur booléenne pour indiquer qu'il ne s'agit pas d'un scénario de mise en cache. Si l'événement CurrentChanged se produit, la modification du parent est une modification de position dans la liste et non une modification de valeur d'élément.

    void bindingSource1_CurrentChanged(object sender, EventArgs e)
    {
        // If the CurrentChanged event occurs, this is not a caching
        // situation.
        cacheChildPosition = false;
    }
    
    ' Handle the current changed event. This event occurs when
    ' the current item is changed, but not when a field of the current
    ' item is changed.
    Private Sub bindingSource1_CurrentChanged(ByVal sender As Object, _
        ByVal e As EventArgs) Handles bindingSource1.CurrentChanged
        ' If the CurrentChanged event occurs, this is not a caching 
        ' situation.
        cacheChildPosition = False
    
    End Sub
    

Pour réinitialiser la position de l'enfant

  1. Gérez l’événement PositionChanged pour le CurrencyManager de la liaison enfant.

  2. Réinitialisez la position de la table enfant à la position mise en cache enregistrée lors de la procédure précédente.

    void relatedCM_PositionChanged(object sender, EventArgs e)
    {
        // Check to see if this is a caching situation.
        if (cacheChildPosition && cachePositionCheckBox.Checked)
        {
            CurrencyManager relatedCM = sender as CurrencyManager;
    
            // If so, check to see if the current position is
            // not equal to the cached position and the cached
            // position is not out of bounds.
            if (relatedCM.Position != cachedPosition && cachedPosition
                > 0 && cachedPosition < relatedCM.Count)
            {
                relatedCM.Position = cachedPosition;
                cachedPosition = -1;
            }
        }
    }
    
    Private Sub relatedCM_PositionChanged(ByVal sender As Object, ByVal e As EventArgs) 
        ' Check to see if this is a caching situation.
        If cacheChildPosition AndAlso cachePositionCheckBox.Checked Then
            Dim relatedCM As CurrencyManager = sender
            
            ' If so, check to see if the current position is 
            ' not equal to the cached position and the cached 
            ' position is not out of bounds.
            If relatedCM.Position <> cachedPosition AndAlso _
                cachedPosition > 0 AndAlso cachedPosition < _
                relatedCM.Count Then
                relatedCM.Position = cachedPosition
                cachedPosition = -1
            End If
        End If
    End Sub
    

Exemple

L'exemple suivant montre comment enregistrer la position actuelle sur le CurrencyManager pour une table enfant et réinitialiser la position après qu'une modification a été effectuée sur la table parente. Cet exemple contient deux contrôles DataGridView liés à deux tables dans un DataSet à l'aide d'un composant BindingSource. Une relation est établie entre les deux tables et la relation est ajoutée au DataSet. La position dans la table enfant est initialement définie sur la troisième ligne à des fins de démonstration.

using System;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace BT2
{
    public class Form1 : Form
    {
        public Form1()
        {
            InitializeControlsAndDataSource();
        }

        // Declare the controls to be used.
        private BindingSource bindingSource1;
        private DataGridView dataGridView1;
        private Button button1;
        private DataGridView dataGridView2;
        private CheckBox cachePositionCheckBox;
        public DataSet set1;

        private void InitializeControlsAndDataSource()
        {
            // Initialize the controls and set location, size and
            // other basic properties.
            this.dataGridView1 = new DataGridView();
            this.bindingSource1 = new BindingSource();
            this.button1 = new Button();
            this.dataGridView2 = new DataGridView();
            this.cachePositionCheckBox = new System.Windows.Forms.CheckBox();
            this.dataGridView1.ColumnHeadersHeightSizeMode =
                DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.dataGridView1.Dock = DockStyle.Top;
            this.dataGridView1.Location = new Point(0, 20);
            this.dataGridView1.Size = new Size(292, 170);
            this.button1.Location = new System.Drawing.Point(18, 175);
            this.button1.Size = new System.Drawing.Size(125, 23);

            button1.Text = "Clear Parent Field";
            this.button1.Click += new System.EventHandler(this.button1_Click);
            this.dataGridView2.ColumnHeadersHeightSizeMode =
                System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.dataGridView2.Location = new System.Drawing.Point(0, 225);
            this.dataGridView2.Size = new System.Drawing.Size(309, 130);
            this.cachePositionCheckBox.AutoSize = true;
            this.cachePositionCheckBox.Checked = true;
            this.cachePositionCheckBox.Location = new System.Drawing.Point(150, 175);
            this.cachePositionCheckBox.Name = "radioButton1";
            this.cachePositionCheckBox.Size = new System.Drawing.Size(151, 17);
            this.cachePositionCheckBox.Text = "Cache and restore position";
            this.ClientSize = new System.Drawing.Size(325, 420);
            this.Controls.Add(this.dataGridView1);
            this.Controls.Add(this.cachePositionCheckBox);
            this.Controls.Add(this.dataGridView2);
            this.Controls.Add(this.button1);

            // Initialize the data.
            set1 = InitializeDataSet();

            // Set the data source to the DataSet.
            bindingSource1.DataSource = set1;

            //Set the DataMember to the Menu table.
            bindingSource1.DataMember = "Customers";

            // Add the control data bindings.
            dataGridView1.DataSource = bindingSource1;

            // Set the data source and member for the second DataGridView.
            dataGridView2.DataSource = bindingSource1;
            dataGridView2.DataMember = "custOrders";

            // Get the currency manager for the customer orders binding.
            CurrencyManager relatedCM =
                bindingSource1.GetRelatedCurrencyManager("custOrders");

            // Set the position in the child table for demonstration purposes.
            relatedCM.Position = 3;

            // Handle the current changed event. This event occurs when
            // the current item is changed, but not when a field of the current
            // item is changed.
            bindingSource1.CurrentChanged +=
                new EventHandler(bindingSource1_CurrentChanged);

            // Handle the two events for caching and resetting the position.
            relatedCM.ListChanged += new ListChangedEventHandler(relatedCM_ListChanged);
            relatedCM.PositionChanged
                += new EventHandler(relatedCM_PositionChanged);

            // Set cacheing to true in case current changed event
            // occurred on set up.
            cacheChildPosition = true;
        }

        // Establish the data set with two tables and a relationship
        // between them.
        private DataSet InitializeDataSet()
        {
            set1 = new DataSet();
            // Declare the DataSet and add a table and column.
            set1.Tables.Add("Customers");
            set1.Tables[0].Columns.Add("CustomerID");
            set1.Tables[0].Columns.Add("Customer Name");
            set1.Tables[0].Columns.Add("Contact Name");

            // Add some rows to the table.
            set1.Tables["Customers"].Rows.Add("c1", "Fabrikam, Inc.", "Ellen Adams");
            set1.Tables[0].Rows.Add("c2", "Lucerne Publishing", "Don Hall");
            set1.Tables[0].Rows.Add("c3", "Northwind Traders", "Lori Penor");
            set1.Tables[0].Rows.Add("c4", "Tailspin Toys", "Michael Patten");
            set1.Tables[0].Rows.Add("c5", "Woodgrove Bank", "Jyothi Pai");

            // Declare the DataSet and add a table and column.
            set1.Tables.Add("Orders");
            set1.Tables[1].Columns.Add("CustomerID");
            set1.Tables[1].Columns.Add("OrderNo");
            set1.Tables[1].Columns.Add("OrderDate");

            // Add some rows to the table.
            set1.Tables[1].Rows.Add("c1", "119", "10/04/2006");
            set1.Tables[1].Rows.Add("c1", "149", "10/10/2006");
            set1.Tables[1].Rows.Add("c1", "159", "10/12/2006");
            set1.Tables[1].Rows.Add("c2", "169", "10/10/2006");
            set1.Tables[1].Rows.Add("c2", "179", "10/10/2006");
            set1.Tables[1].Rows.Add("c2", "189", "10/12/2006");
            set1.Tables[1].Rows.Add("c3", "122", "10/04/2006");
            set1.Tables[1].Rows.Add("c4", "130", "10/10/2006");
            set1.Tables[1].Rows.Add("c5", "1.29", "10/14/2006");

            DataRelation dr = new DataRelation("custOrders",
                set1.Tables["Customers"].Columns["CustomerID"],
                set1.Tables["Orders"].Columns["CustomerID"]);
            set1.Relations.Add(dr);
            return set1;
        }
        private int cachedPosition = -1;
        private bool cacheChildPosition = true;

        void relatedCM_ListChanged(object sender, ListChangedEventArgs e)
        {
            // Check to see if this is a caching situation.
            if (cacheChildPosition && cachePositionCheckBox.Checked)
            {
                // If so, check to see if it is a reset situation, and the current
                // position is greater than zero.
                CurrencyManager relatedCM = sender as CurrencyManager;
                if (e.ListChangedType == ListChangedType.Reset && relatedCM.Position > 0)

                    // If so, cache the position of the child table.
                    cachedPosition = relatedCM.Position;
            }
        }
        void bindingSource1_CurrentChanged(object sender, EventArgs e)
        {
            // If the CurrentChanged event occurs, this is not a caching
            // situation.
            cacheChildPosition = false;
        }
        void relatedCM_PositionChanged(object sender, EventArgs e)
        {
            // Check to see if this is a caching situation.
            if (cacheChildPosition && cachePositionCheckBox.Checked)
            {
                CurrencyManager relatedCM = sender as CurrencyManager;

                // If so, check to see if the current position is
                // not equal to the cached position and the cached
                // position is not out of bounds.
                if (relatedCM.Position != cachedPosition && cachedPosition
                    > 0 && cachedPosition < relatedCM.Count)
                {
                    relatedCM.Position = cachedPosition;
                    cachedPosition = -1;
                }
            }
        }
        int count = 0;
        private void button1_Click(object sender, EventArgs e)
        {
            // For demo purposes--modifies a value in the first row of the
            // parent table.
            DataRow row1 = set1.Tables[0].Rows[0];
            row1[1] = DBNull.Value;
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.Text
Imports System.Windows.Forms



Public Class Form1
    Inherits Form
    
    Public Sub New() 
        InitializeControlsAndDataSource()
    
    End Sub
    
    ' Declare the controls to be used.
    Private WithEvents bindingSource1 As BindingSource
    Private dataGridView1 As DataGridView
    Private WithEvents button1 As Button
    Private dataGridView2 As DataGridView
    Private cachePositionCheckBox As CheckBox
    Public set1 As DataSet
    
    
    Private Sub InitializeControlsAndDataSource() 
        ' Initialize the controls and set location, size and 
        ' other basic properties.
        Me.dataGridView1 = New DataGridView()
        Me.bindingSource1 = New BindingSource()
        Me.button1 = New Button()
        Me.dataGridView2 = New DataGridView()
        Me.cachePositionCheckBox = New System.Windows.Forms.CheckBox()
        Me.dataGridView1.ColumnHeadersHeightSizeMode = _
            DataGridViewColumnHeadersHeightSizeMode.AutoSize
        Me.dataGridView1.Dock = DockStyle.Top
        Me.dataGridView1.Location = New Point(0, 20)
        Me.dataGridView1.Size = New Size(292, 170)
        Me.button1.Location = New System.Drawing.Point(18, 175)
        Me.button1.Size = New System.Drawing.Size(125, 23)
        
        button1.Text = "Clear Parent Field"

        Me.dataGridView2.ColumnHeadersHeightSizeMode = _
            System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize
        Me.dataGridView2.Location = New System.Drawing.Point(0, 225)
        Me.dataGridView2.Size = New System.Drawing.Size(309, 130)
        Me.cachePositionCheckBox.AutoSize = True
        Me.cachePositionCheckBox.Checked = True
        Me.cachePositionCheckBox.Location = New System.Drawing.Point(150, 175)
        Me.cachePositionCheckBox.Name = "radioButton1"
        Me.cachePositionCheckBox.Size = New System.Drawing.Size(151, 17)
        Me.cachePositionCheckBox.Text = "Cache and restore position"
        Me.ClientSize = New System.Drawing.Size(325, 420)
        Me.Controls.Add(Me.dataGridView1)
        Me.Controls.Add(Me.cachePositionCheckBox)
        Me.Controls.Add(Me.dataGridView2)
        Me.Controls.Add(Me.button1)
        
        ' Initialize the data.
        set1 = InitializeDataSet()
        
        ' Set the data source to the DataSet.
        bindingSource1.DataSource = set1
        
        'Set the DataMember to the Menu table.
        bindingSource1.DataMember = "Customers"
        
        ' Add the control data bindings.
        dataGridView1.DataSource = bindingSource1
        
        ' Set the data source and member for the second DataGridView.
        dataGridView2.DataSource = bindingSource1
        dataGridView2.DataMember = "custOrders"
        
        ' Get the currency manager for the customer orders binding.
        Dim relatedCM As CurrencyManager = _
            bindingSource1.GetRelatedCurrencyManager("custOrders")

        ' Handle the two events for caching and resetting the position.
        AddHandler relatedCM.ListChanged, AddressOf relatedCM_ListChanged
        AddHandler relatedCM.PositionChanged, AddressOf relatedCM_PositionChanged
        
        ' Set the position in the child table for demonstration purposes.
        relatedCM.Position = 3

        ' Set cacheing to true in case current changed event
        ' occurred on set up.
        cacheChildPosition = True
      
    
    End Sub
    
    
    
    ' Establish the data set with two tables and a relationship
    ' between them.
    Private Function InitializeDataSet() As DataSet 
        set1 = New DataSet()
        ' Declare the DataSet and add a table and column.
        set1.Tables.Add("Customers")
        set1.Tables(0).Columns.Add("CustomerID")
        set1.Tables(0).Columns.Add("Customer Name")
        set1.Tables(0).Columns.Add("Contact Name")
        
        ' Add some rows to the table.
        set1.Tables("Customers").Rows.Add("c1", "Fabrikam, Inc.", _
            "Ellen Adams")
        set1.Tables(0).Rows.Add("c2", "Lucerne Publishing", "Don Hall")
        set1.Tables(0).Rows.Add("c3", "Northwind Traders", "Lori Penor")
        set1.Tables(0).Rows.Add("c4", "Tailspin Toys", "Michael Patten")
        set1.Tables(0).Rows.Add("c5", "Woodgrove Bank", "Jyothi Pai")
        
        ' Declare the DataSet and add a table and column.
        set1.Tables.Add("Orders")
        set1.Tables(1).Columns.Add("CustomerID")
        set1.Tables(1).Columns.Add("OrderNo")
        set1.Tables(1).Columns.Add("OrderDate")
        
        ' Add some rows to the table.
        set1.Tables(1).Rows.Add("c1", "119", "10/04/2006")
        set1.Tables(1).Rows.Add("c1", "149", "10/10/2006")
        set1.Tables(1).Rows.Add("c1", "159", "10/12/2006")
        set1.Tables(1).Rows.Add("c2", "169", "10/10/2006")
        set1.Tables(1).Rows.Add("c2", "179", "10/10/2006")
        set1.Tables(1).Rows.Add("c2", "189", "10/12/2006")
        set1.Tables(1).Rows.Add("c3", "122", "10/04/2006")
        set1.Tables(1).Rows.Add("c4", "130", "10/10/2006")
        set1.Tables(1).Rows.Add("c5", "1.29", "10/14/2006")
        
        Dim dr As New DataRelation("custOrders", _
            set1.Tables("Customers").Columns("CustomerID"), _
            set1.Tables("Orders").Columns("CustomerID"))
        set1.Relations.Add(dr)
        Return set1
    
    End Function '
    Private cachedPosition As Integer = - 1
    Private cacheChildPosition As Boolean = True
    
    Private Sub relatedCM_ListChanged(ByVal sender As Object, _
        ByVal e As ListChangedEventArgs)
        ' Check to see if this is a caching situation.
        If cacheChildPosition AndAlso cachePositionCheckBox.Checked Then
            ' If so, check to see if it is a reset situation, and the current
            ' position is greater than zero.
            Dim relatedCM As CurrencyManager = sender
            If e.ListChangedType = ListChangedType.Reset _
                AndAlso relatedCM.Position > 0 Then

                ' If so, cache the position of the child table.
                cachedPosition = relatedCM.Position
            End If
        End If

    End Sub

    ' Handle the current changed event. This event occurs when
    ' the current item is changed, but not when a field of the current
    ' item is changed.
    Private Sub bindingSource1_CurrentChanged(ByVal sender As Object, _
        ByVal e As EventArgs) Handles bindingSource1.CurrentChanged
        ' If the CurrentChanged event occurs, this is not a caching 
        ' situation.
        cacheChildPosition = False

    End Sub

    Private Sub relatedCM_PositionChanged(ByVal sender As Object, ByVal e As EventArgs) 
        ' Check to see if this is a caching situation.
        If cacheChildPosition AndAlso cachePositionCheckBox.Checked Then
            Dim relatedCM As CurrencyManager = sender
            
            ' If so, check to see if the current position is 
            ' not equal to the cached position and the cached 
            ' position is not out of bounds.
            If relatedCM.Position <> cachedPosition AndAlso _
                cachedPosition > 0 AndAlso cachedPosition < _
                relatedCM.Count Then
                relatedCM.Position = cachedPosition
                cachedPosition = -1
            End If
        End If
    End Sub

    Private count As Integer = 0
    
    Private Sub button1_Click(ByVal sender As Object, _
        ByVal e As EventArgs) Handles button1.Click
        ' For demo purposes--modifies a value in the first row of the
        ' parent table.
        Dim row1 As DataRow = set1.Tables(0).Rows(0)
        row1(1) = DBNull.Value
    End Sub
     
    <STAThread()>  _
    Shared Sub Main() 
        Application.EnableVisualStyles()
        Application.SetCompatibleTextRenderingDefault(False)
        Application.Run(New Form1())
    
    End Sub
End Class

Pour tester l'exemple de code, procédez comme suit :

  1. Exécutez l'exemple.

  2. Assurez-vous que la case Cache et réinitialisation de position est cochée.

  3. Cliquez sur le bouton Effacer le champ parent pour provoquer une modification dans un champ de la table parent. Notez que la ligne sélectionnée dans la table enfant ne change pas.

  4. Fermez puis réexécutez l'exemple. Cette opération est nécessaire car le comportement de réinitialisation se produit uniquement lors de la première modification sur la ligne parente.

  5. Décochez la case Cache et réinitialisation de position.

  6. Cliquez sur le bouton Effacer le champ parent. Notez que la ligne sélectionnée dans la table enfant passe en première ligne.

Compilation du code

Cet exemple nécessite :

  • Références aux assemblys System, System.Data, System.Drawing, System.Windows.Forms et System.Xml.

Voir aussi