Click here to Skip to main content
15,889,216 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
This is my first attempt at using a WPF ListView control. I have used the WinForms ListView control extensively but that hasn't helped me solve this issue. I get these two errors when I add a multi-column item to the WPF ListView control.

Using the Visual Studio Debugger Quick Watch, I can see that the DirectCast((New System.Linq.SystemCore_EnumerableDebugView(lstCSchedule.Items).Items(0)), System.Windows.Controls.ContentControl).Content object contains both the Name and the RecordDateTime property names and values but nothing is displayed in the ListView control other than the column headings and a blank row. That tells me that the data is getting put into the ListView control but it is not displayed by the ListView control.

What syntax can I use to properly add a row to the ListView Items collection so that it displays in the ListView control and doesn't cause an error message?

Run-Time Error Messages
System.Windows.Data Error: 40 : BindingExpression path error: 'Name' property not found on 'object' ''CRecordSchedule' (HashCode=18437496)'. BindingExpression:Path=Name; DataItem='CRecordSchedule' (HashCode=18437496); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

System.Windows.Data Error: 40 : BindingExpression path error: 'RecordDateTime' property not found on 'object' ''CRecordSchedule' (HashCode=18437496)'. BindingExpression:Path=RecordDateTime; DataItem='CRecordSchedule' (HashCode=18437496); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')


XAML WPF Definition of the ListView Control
XAML
<ListView x:Name="lstCSchedule" HorizontalAlignment="Left" Height="108" Margin="27,88,0,0" VerticalAlignment="Top">
    <ListView.View>
        <GridView>
            <GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}"  />
            <GridViewColumn Width="90" Header="Record DateTime" DisplayMemberBinding="{Binding RecordDateTime}" />
        </GridView>
    </ListView.View>
</ListView>


The CRecordSchedule Class Definition
VB
Public Class CRecordSchedule
    Public Name As String
    Public RecordDateTime As String
    Public Sub New(n As String, r As String)
        Name = n
        RecordDateTime = r
    End Sub
End Class


The code that adds one new item to the ListView control
VB
Dim Schedule As New CRecordSchedule("A Test Show Title", "06/14/18 7:00PM")
Dim lvItem As New ListViewItem
lvItem.Content = Schedule
lstCSchedule.Items.Add(lvItem)


What I have tried:

1. lstCSchedule.Items.Add(Schedule)
2. Add an array to the ListView Items collection instead of a class object.
3. DisplayMemberBinding="{Binding Path=Name}" and DisplayMemberBinding="{Binding Path=RecordDateTime}"
4. Numerous other iterations of basically the same attempted solutions.
Posted
Updated 3-Jun-18 6:55am

The error message is the best indicator of what is required.

WPF Data Binding[^] is far superior and simpler to the WinForm Data Binding system. Once you invest the time to learn how data binding works in WPF, you won't want to look back.

Here is an example for you to show you quickly how Data Binding works:

WPF Binding for property updates requires a PropertyChanged event to be fired for each property. For collections, we can use a specialized collection class called ObservableCollection which fires a CollectionChanged event.

For the properties, below is a base class to simplify the repetitive code required for each property in a class:
VB
Public MustInherit Class ObservableBase
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.PropertyChanged

    Public Sub [Set](Of TValue)(ByRef field As TValue, ByVal newValue As TValue,
                                <CallerMemberName> ByVal Optional propertyName As String = "")
        If EqualityComparer(Of TValue).[Default].Equals(field, Nothing) OrElse Not field.Equals(newValue) Then
            field = newValue
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))

        End If
    End Sub

End Class

For this example, I will use a PersonModel class which inherits the ObservableBase base class above:
VB
Public Class PersonModel
    Inherits ObservableBase

    Private mName As String

    Public Property Name As String
        Get
            Return mName
        End Get
        Set(ByVal value As String)
            [Set](mName, value)
        End Set
    End Property

    Private mAge As Integer

    Public Property Age As Integer
        Get
            Return mAge
        End Get
        Set(ByVal value As Integer)
            [Set](mAge, value)
        End Set
    End Property
End Class

Now we can create our collection and add a event to demonstrate the binding system in action:
VB
Imports System.Collections.ObjectModel

Class MainWindow

    Public Sub New()
        InitializeComponent()
        DataContext = Me
        Mock()
    End Sub

    Public Property Persons As ObservableCollection(Of PersonModel) = New ObservableCollection(Of PersonModel)()
    Private rand As Random = New Random()

    Private Sub Mock()
        For i As Integer = 0 To 10 - 1
            Persons.Add(New PersonModel With {
                    .Name = String.Format("Person {0}", i),
                    .Age = rand.[Next](20, 50)
                })
        Next
    End Sub

    Private Sub Button_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        For Each person In Persons
            person.Age = rand.[Next](20, 50)
        Next
    End Sub
End Class

In the above code-behind page, I set the form's DataContext to the code-behind page itself. This will allow the UI (XAML) to see the binding notifications and update the display.

Lastly, we can bind the properties of the code-behind to the UI elements:
XML
<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        mc:Ignorable="d"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfSimpleBindingVB"
        Title="MainWindow"
    WindowStartupLocation="CenterScreen" Height="400" Width="300">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Grid.Resources>
            <DataTemplate DataType="{x:Type local:PersonModel}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Text="{Binding Name}" Margin="10 3"/>
                    <TextBlock Text="{Binding Age}" Margin="10 3"
                               Grid.Column="1"/>
                </Grid>
            </DataTemplate>
        </Grid.Resources>

        <ListBox ItemsSource="{Binding Persons}">
            <ListBox.ItemContainerStyle>
                <Style TargetType="ListBoxItem">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
        <Button Content="Randomize" Padding="10 5" Margin="10"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Grid.Row="1" Click="Button_Click"/>
    </Grid>

</Window>

The above example uses a ListBox control. The principles for the ListView is exactly the same. Here is the UI using the ListView instead:
XML
<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        mc:Ignorable="d"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfSimpleBindingVB"
        Title="MainWindow"
    WindowStartupLocation="CenterScreen" Height="400" Width="300">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <ListView ItemsSource="{Binding Persons}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"  />
                    <GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}" />
                </GridView>
            </ListView.View>
        </ListView>

        <Button Content="Randomize" Padding="10 5" Margin="10"
                HorizontalAlignment="Center" VerticalAlignment="Center"
                Grid.Row="1" Click="Button_Click"/>
    </Grid>

</Window>

This example is using the same data binding technique used in the MVVM (Model View ViewModel) Design Pattern except the ViewModel is in the code behind. MVVM is a little bit more involved and is beyond the scope of this answer but worthwhile investing the time in learning.
 
Share this answer
 
Comments
Graeme_Grant 3-Jun-18 17:26pm    
Hi Mike,

It actually answers your question and the next one that you will be asking when you change property data.

With WPF you work with the data and the UI will reflect the changes. Your code is working directly with the controls and not the data. When you work with the data, it is far simpler and unbound from specific controls. The above solution demonstrates this.

But to answer your specific question, to add a new record, you simply create a new record class and add it to the ObservableCollection and the UI will reflect the change - a new row added in this case:
Persons.Add(New PersonModel With {
    .Name = String.Format("Person {0}", i),
    .Age = rand.[Next](20, 50)
})
Mike Meinz 11-Jun-18 10:08am    
Thanks. Your solution helped a lot. There are so many partial examples. It is good to be able to see a complete working example.
Graeme_Grant 11-Jun-18 21:06pm    
You are most welcome :)
I have solved my problem.

I changed the class definition by adding a Public Property for Name and RecordDateTime.
VB
Public Class CSchedule
    Private mName As String
    Private mRecordDateTime As String
    Public Sub New(ByVal Name As String, ByVal RecordDateTime As String)
        mName = Name
        mRecordDateTime = RecordDateTime
    End Sub
    Public Property Name As String
        Get
            Name = mName
        End Get
        Set(value As String)
            mName = value
        End Set
    End Property
    Public Property RecordDateTime As String
        Get
            RecordDateTime = mRecordDateTime
        End Get
        Set(value As String)
            mRecordDateTime = value
        End Set
    End Property
End Class


VB
Dim Schedule As New CRecordSchedule(rs.GetString(rs.GetOrdinal("name")), Format(rs.GetDateTime(rs.GetOrdinal("rundatetime")), "MM/dd/yy hh:mm:sstt"))
lstCSchedule.Items.Add(Schedule)


To diagnose this issue, I added trace declarations to the XAML for the ListView columns. One of the trace messages said that the Accessor was null. I guessed that meant that I had to explicitly declare a Public Property for Name and RecordDateTime. The conclusion is that the binding mechanism can't find the values in a class object unless the properties are explicitly declared.
XAML
<ListView x:Name="lstCSchedule" HorizontalAlignment="Left" Height="108" Margin="1,88,0,0" VerticalAlignment="Top" Grid.Column="1" >
            <ListView.View>
                <GridView>
                    <GridViewColumn Width="90" Header="Name" PresentationTraceSources.TraceLevel="High"  DisplayMemberBinding="{Binding Name,diag:PresentationTraceSources.TraceLevel=High}" />
                    <GridViewColumn Width="90" Header="Record DateTime" PresentationTraceSources.TraceLevel="High" DisplayMemberBinding="{Binding RecordDateTime,diag:PresentationTraceSources.TraceLevel=High}" />
                </GridView>
            </ListView.View>
</ListView>
 
Share this answer
 
v2

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900