如何:确保子表中的选定行保持在正确的位置

通常当在 Windows 窗体中使用数据绑定时,将显示称为父/子视图或母版/详细视图中的数据。 这是指一个数据绑定方案,其中来自同一源的数据将显示在两个控件中。 更改一个控件中的选定内容会导致在第二个控件中显示的数据变动。 例如,第一个控件可能会包含一个客户列表,而第二个控件则可能包含与第一个控件中选定客户相关的订单列表。

开始使用.NET Framework 2.0 版本,当在父/子视图中显示数据时,可能需要采取额外步骤来确保子表中当前所选的行不会重置为表的第一行。 为了执行此操作,必须缓存子表位置并在父表发生更改后将其重置。 父表某一行中的字段第一次更改时通常会发生子表重置。

若要缓存当前子表位置

  1. 则声明一个整数变量,以存储子列表位置,并声明一个布尔变量,以存储是否缓存子表位置。

    private int cachedPosition = -1;
    private bool cacheChildPosition = true;
    
    Private cachedPosition As Integer = - 1
    Private cacheChildPosition As Boolean = True
    
    
  2. 为绑定的 CurrencyManager 处理 ListChanged 事件并检查 ResetListChangedType

  3. 检查 CurrencyManager 的当前位置。 如果它大于列表中第一个条目(通常为 0),则将其保存到一个变量。

    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. 为父货币管理器处理父列表的 CurrentChanged 事件。 在处理程序中,设置布尔值,以指示其不是一个缓存方案。 如果发生了 CurrentChanged,则对父对象的更改是列表位置的更改,而不是项目值的更改。

    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
    

若要重置子表位置

  1. 为子绑定的 CurrencyManager处理 PositionChanged 事件。

  2. 将子表位置重置到前述过程中保存的缓存位置。

    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
    

示例

以下示例演示了如何在子表的 CurrencyManager 上保存当前位置,并在父表上的编辑完成后重置该位置。 此示例包含通过使用 BindingSource 组件绑定到 DataSet 中的两个表的两个 DataGridView 控件。 两个表之间建立了关系并将此关系添加到了 DataSet。 出于演示目的,子表中的位置最初设置为第三行。

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

若要测试代码示例,请执行以下步骤:

  1. 运行示例。

  2. 请确保“缓存并重置位置”复选框处于选中状态。

  3. 单击“清除父字段”按钮会导致父表的某个字段发生更改。 请注意,子表中的所选行将不会更改。

  4. 关闭,然后再次运行该示例。 需要这样做的原因是重置行为仅在父行中第一次更改时发生。

  5. 清除“缓存并重置位置”复选框。

  6. 单击“清除父字段”按钮。 请注意,子表中的选定的行将更改为第一行。

编译代码

此示例需要:

  • 对 System、System.Data、System.Drawing、System.Windows.Forms 和 System.Xml 程序集的引用。

另请参阅