locked
wpf datagrid - binding question about code behind RRS feed

  • Question

  • Even though the datagrid performs like it should, I'm wondering if my code is not optimal.  When I put breakpoints in the code to follow what it does, I see some repetition that I'm not sure should happen.  It appears to be a very expensive repetition as far as system resources.

    Sample data:

    Xaml:

    <Window x:Class="DentalDB.PartBBilling_Window"
        x:Name="Window"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Part B Billing" Height="300" Width="805" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" Background="#FF336B7B">
        
        <Grid Width="743" Height="259">
            <Grid.RowDefinitions>
                <RowDefinition Height="139*" />
                <RowDefinition Height="122*" />
            </Grid.RowDefinitions>
            
            <DataGrid ItemsSource="{Binding PartB}" AutoGenerateColumns="False" Height="237" HorizontalAlignment="Left" Margin="12,12,0,0" Name="dgPartBBilling" 
                      VerticalAlignment="Top" Width="731" AlternationCount="2" AlternatingRowBackground="LightBlue" Grid.RowSpan="2">
    
                <DataGrid.Columns>
                    <DataGridTemplateColumn x:Name="dPartBBilling_Date" Width="100" Header="Billing Date">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Path=PartBBilling_Date, StringFormat=\{0:d\}}" Width="75"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                        <DataGridTemplateColumn.CellEditingTemplate>
                            <DataTemplate>
                                <Grid FocusManager.FocusedElement="{Binding ElementName= dPartBBilling_Date}"  >
                                    <DatePicker x:Name="dPartBBilling_Date" 
                                        SelectedDate="{Binding Path=PartBBilling_Date}" /> 
                                </Grid>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellEditingTemplate>
                    </DataGridTemplateColumn>
    
                    <DataGridTemplateColumn Header="CPT Code" Width="75">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <ComboBox ItemsSource="{Binding PartBCombo, 
                                            RelativeSource={RelativeSource AncestorType=Window}}" 
                                            DisplayMemberPath="PartBLookup_CPTCode" 
                                            SelectedValuePath="PartBLookup_ProcedureDescription"  
                                            SelectedValue="{Binding PartBBilling_ProcedureName, UpdateSourceTrigger=PropertyChanged}"
                                            SelectedItem="{Binding PartBBilling_CPT}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
    
                    <DataGridTextColumn Header="Procedure Name" Width="250" Binding="{Binding PartBBilling_ProcedureName}" IsReadOnly="True"/>
                    <DataGridTextColumn Header="Tooth #" Width="120" Binding="{Binding PartBBilling_Tooth}" />
                    <DataGridTextColumn Header="Quantity" Width="75" Binding="{Binding PartBBilling_Quantity}" />
                    <DataGridTextColumn Header="Total" Width="75" Binding="{Binding PartBBilling_Total, StringFormat=N2}" IsReadOnly="True"/>
    
                </DataGrid.Columns>
                
            </DataGrid>
    
        </Grid>
    </Window>
    

    Code behind:

    Imports System.Collections.ObjectModel
    Imports System.ComponentModel
    
    Namespace DentalDB
    
        Partial Public Class PartBBilling_Window
            Inherits Window
            Implements INotifyPropertyChanged
    
            Private Sub RaisePropertyChanged(prop As String)
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
            End Sub
    
            Public Sub New()
    
                ' This call is required by the designer.
                InitializeComponent()
    
                ' Add any initialization after the InitializeComponent() call.
    
                'fill combobox with items
                If conn.State = ConnectionState.Closed Then
                    conn.Open()
                End If
    
                cmd = New SqlCommand()
                cmd.Connection = conn
    
                cmd.CommandText = "SELECT CPT, ProcedureName FROM tlkpProcedures_PartB ORDER BY CPT"
                Dim Reader As SqlDataReader = cmd.ExecuteReader()
    
                PartBCombo = New List(Of PartBBilling_Combobox)
    
                If Reader.HasRows Then
                    While Reader.Read()
                        PartBCombo.Add( _
                            New PartBBilling_Combobox() With { _
                                .PartBLookup_CPTCode = Reader("CPT").ToString,
                                .PartBLookup_ProcedureDescription = Reader("ProcedureName").ToString
                           })
                    End While
                End If
    
                Reader.Close()
                conn.Close()
    
    
                'fill datagrid
                PartB = New List(Of PartBBilling)
    
                If conn.State = ConnectionState.Closed Then
                    conn.Open()
                End If
    
                cmd = New SqlCommand()
                cmd.Connection = conn
    
                sSQL = "SELECT " & _
                    "tblBilling_PartB.RW, " & _
                    "tblBilling_PartB.BillingDate, " & _
                    "tblBilling_PartB.CPT, " & _
                    "ISNULL(tlkpProcedures_PartB.ProcedureName, '') AS ProcedureName, " & _
                    "tblBilling_PartB.Tooth, " & _
                    "tblBilling_PartB.Quantity, " & _
                    "ISNULL(tlkpProcedures_PartB.BillingAmount * tblBilling_PartB.Quantity, '') AS Total, " & _
                    "tblBilling_PartB.UniqueID " & _
                    "FROM tblBilling_PartB " & _
                    "LEFT JOIN tlkpProcedures_PartB ON tblBilling_PartB.CPT = tlkpProcedures_PartB.CPT " & _
                    "WHERE RW = " & CInt(RW) & " " & _
                    "ORDER BY tblBilling_PartB.BillingDate DESC"
    
                cmd.CommandText = sSQL
    
                Reader = cmd.ExecuteReader()
    
                If Reader.HasRows Then
                    'bolCheckDate = False
    
                    While Reader.Read()
                        PartB.Add( _
                            New PartBBilling() With { _
                                .PartBBilling_RW = Reader("RW").ToString,
                                .PartBBilling_Date = Reader("BillingDate"),
                                .PartBBilling_CPT = Reader("CPT").ToString,
                                .PartBBilling_ProcedureName = Reader("ProcedureName").ToString,
                                .PartBBilling_Tooth = Reader("Tooth").ToString,
                                .PartBBilling_Quantity = Reader("Quantity")
                           })
                    End While
                End If
    
                conn.Close()
    
                DataContext = Me
    
            End Sub
    
            Public Property PartBCombo As List(Of PartBBilling_Combobox)
                Get
                    Return _PartBCombo
                End Get
                Set(value As List(Of PartBBilling_Combobox))
                    _PartBCombo = value
                End Set
            End Property
            Private _PartBCombo As List(Of PartBBilling_Combobox)
    
            Public Property PartB As List(Of PartBBilling)
                Get
                    Return _PartB
                End Get
                Set(value As List(Of PartBBilling))
                    _PartB = value
                End Set
            End Property
            Private _PartB As List(Of PartBBilling)
    
            Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    
        End Class
    
    End Namespace
    

    Class for combobox items:

    Imports System
    
    Namespace DentalDB
    
        Public Class PartBBilling_Combobox
    
            Private _PartBLookup_CPTCode As String
            Public Property PartBLookup_CPTCode As String
                Get
                    Return _PartBLookup_CPTCode
                End Get
                Set(value As String)
                    _PartBLookup_CPTCode = value
                End Set
            End Property
    
            Private _PartBLookup_ProcedureDescription As String
            Public Property PartBLookup_ProcedureDescription As String
                Get
                    Return _PartBLookup_ProcedureDescription
                End Get
                Set(value As String)
                    _PartBLookup_ProcedureDescription = value
                End Set
            End Property
    
        End Class
    
    End Namespace
    

    The combobox class code above is executed 45 times, one for each item.  This is what I would expect.

    Then it's executed multiple times again while,   I presume,   the datagrid is loading with data.  But I can't pinpoint down where this happens.  Using the sample data, for the first row "Bitewing-Single Film", this class will execute 55 times, 45 times to go thru the combo items, then another 10 times to get to "Bitewing-Single Film" which is the 10th item in the list (a breakpoint in the Get for the ProcedureName confirms this).

    For the next row "Limited Oral Exam/Problem focused", the code will execute 56 times, 45 for each item in the combo, the 11 more times because "Limited Oral..." is the 11th item.

    "Panoramic Film" is the 15th item.  Code is executed 45 + 15 times.

    I think the combo is trying to match the CPT with the Procedure Name but this constant looping seems to be overkill.

    Is this just its nature or is there something  that can be done to make this more streamlined?


    Thanks.

    Tuesday, November 19, 2013 4:37 PM

Answers

  • Hi,

    >>Using the sample data, for the first row "Bitewing-Single Film", this class will execute 55 times, 45 times to go thru the combo items, then another 10 times to get to "Bitewing-Single Film" which is the 10th item in the list

    I agree with what you said, every time a new PartBBilling_Combobox object instance is added to the PartBCombo, the property you defined will be called, as we see, the PartBBilling_Combobox class will be executed.

    While Reader.Read()
           PartBCombo.Add( _
                  New PartBBilling_Combobox() With { _
                            .PartBLookup_CPTCode = Reader("CPT").ToString,
                            .PartBLookup_ProcedureDescription = Reader("ProcedureName").ToString
                  })
    End While

    In the XAML, you bind ComboBox's ItemsSource to the PartBCombo property, and the DisplayMemberPath is bound by the PartBLookup_CPTCode, so it will cause multiple access to the property:

    <ComboBox ItemsSource="{Binding PartBCombo, 
                      RelativeSource={RelativeSource AncestorType=Window}}" 
                      DisplayMemberPath="PartBLookup_CPTCode" 
                      SelectedValuePath="PartBLookup_ProcedureDescription"  
                      SelectedValue="{Binding PartBBilling_ProcedureName, UpdateSourceTrigger=PropertyChanged}"
                      SelectedItem="{Binding PartBBilling_CPT}"/>

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    • Proposed as answer by Leo (Apple) Yang Wednesday, November 20, 2013 7:33 AM
    • Marked as answer by db_dweeb Wednesday, November 20, 2013 2:18 PM
    Wednesday, November 20, 2013 4:55 AM
  • In addition to what Franklin said.

    .

    I think you should consider MVVM and putting the data manipulation logic into a viewmodel instead of code behind. 

    I would also use a stored proc or LINQ rather than sql string.

    And entity framework rather than straight ADO.

    As it is I'd open the connection and datareader with USING.

    .

    As a minimum.

    Don't add directly into your public collection.

    Build a private collection.

    Fill it.

    Set the public = private.

    Raise property changed on the public collection.

    Wednesday, November 20, 2013 8:41 AM
  • A getter of a source property that is bound to a column or element in the DataGrid will be called at least once per row in the grid. This is an excpected behaviour. The Mode property of the binding sets the direction of the data flow: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode(v=vs.110).aspx

    Also, if the DataGrid control contains enough rows, the visual containers representing the rows may be virtualized. This means that elements will be added and removed from the visual tree as you scroll through the grid and this causes the getters of the source properties to be called again.

    • Marked as answer by db_dweeb Wednesday, November 20, 2013 2:17 PM
    Wednesday, November 20, 2013 12:18 PM

All replies

  • Hi,

    >>Using the sample data, for the first row "Bitewing-Single Film", this class will execute 55 times, 45 times to go thru the combo items, then another 10 times to get to "Bitewing-Single Film" which is the 10th item in the list

    I agree with what you said, every time a new PartBBilling_Combobox object instance is added to the PartBCombo, the property you defined will be called, as we see, the PartBBilling_Combobox class will be executed.

    While Reader.Read()
           PartBCombo.Add( _
                  New PartBBilling_Combobox() With { _
                            .PartBLookup_CPTCode = Reader("CPT").ToString,
                            .PartBLookup_ProcedureDescription = Reader("ProcedureName").ToString
                  })
    End While

    In the XAML, you bind ComboBox's ItemsSource to the PartBCombo property, and the DisplayMemberPath is bound by the PartBLookup_CPTCode, so it will cause multiple access to the property:

    <ComboBox ItemsSource="{Binding PartBCombo, 
                      RelativeSource={RelativeSource AncestorType=Window}}" 
                      DisplayMemberPath="PartBLookup_CPTCode" 
                      SelectedValuePath="PartBLookup_ProcedureDescription"  
                      SelectedValue="{Binding PartBBilling_ProcedureName, UpdateSourceTrigger=PropertyChanged}"
                      SelectedItem="{Binding PartBBilling_CPT}"/>

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    • Proposed as answer by Leo (Apple) Yang Wednesday, November 20, 2013 7:33 AM
    • Marked as answer by db_dweeb Wednesday, November 20, 2013 2:18 PM
    Wednesday, November 20, 2013 4:55 AM
  • In addition to what Franklin said.

    .

    I think you should consider MVVM and putting the data manipulation logic into a viewmodel instead of code behind. 

    I would also use a stored proc or LINQ rather than sql string.

    And entity framework rather than straight ADO.

    As it is I'd open the connection and datareader with USING.

    .

    As a minimum.

    Don't add directly into your public collection.

    Build a private collection.

    Fill it.

    Set the public = private.

    Raise property changed on the public collection.

    Wednesday, November 20, 2013 8:41 AM
  • A getter of a source property that is bound to a column or element in the DataGrid will be called at least once per row in the grid. This is an excpected behaviour. The Mode property of the binding sets the direction of the data flow: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode(v=vs.110).aspx

    Also, if the DataGrid control contains enough rows, the visual containers representing the rows may be virtualized. This means that elements will be added and removed from the visual tree as you scroll through the grid and this causes the getters of the source properties to be called again.

    • Marked as answer by db_dweeb Wednesday, November 20, 2013 2:17 PM
    Wednesday, November 20, 2013 12:18 PM