Mike Meinz Ответов: 2

Как добавить один элемент, содержащий несколько столбцов, в элемент управления WPF listview


Это моя первая попытка использовать элемент управления WPF ListView. Я широко использовал элемент управления WinForms ListView, но это не помогло мне решить эту проблему. Я получаю эти две ошибки, когда добавляю многоколоночный элемент в элемент управления WPF ListView.

Используя быстрый просмотр отладчика Visual Studio, я вижу, что DirectCast((Новая Система.Linq.SystemCore_EnumerableDebugView(lstCSchedule.Утварь).Пункты(0)), Система.Окна.Управления.ContentControl).Содержание объект содержит как имя, так и имена и значения свойств RecordDateTime, но в элементе управления ListView не отображается ничего, кроме заголовков столбцов и пустой строки. Это говорит мне о том, что данные помещаются в элемент управления ListView, но он не отображается элементом управления ListView.

Какой синтаксис можно использовать для правильного добавления строки в коллекцию элементов ListView, чтобы она отображалась в элементе управления ListView и не вызывала сообщения об ошибке?

Сообщения Об Ошибках Во Время Выполнения
Система.Окна.Данные ошибка: 40 : BindingExpression путь ошибка: 'имя' имущество не нашел на 'объект' "CRecordSchedule' (хэш-код=18437496)'. BindingExpression:путь=имя; CRecordSchedule элемента данных='' (хэш-код=18437496); целевой элемент 'блоком' (имя="); целевое свойство "текст" (тип "строка")

Система.Окна.Данные ошибка: 40 : BindingExpression путь ошибка: 'RecordDateTime' имущество не нашел на 'объект' "CRecordSchedule' (хэш-код=18437496)'. BindingExpression:путь=RecordDateTime; CRecordSchedule элемента данных='' (хэш-код=18437496); целевой элемент 'блоком' (имя="); целевое свойство "текст" строку ('тип')


XAML WPF определение элемента управления ListView
<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>


Определение Класса CRecordSchedule
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


Код, который добавляет новый элемент управления ListView
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)


Что я уже пробовал:

1. lstCSchedule.Предметы.Добавить(Расписание)
2. Добавьте массив в коллекцию элементов ListView вместо объекта класса.
3. DisplayMemberBinding="{Binding Path=Name}" и DisplayMemberBinding="{Binding Path=RecordDateTime}"
4. многочисленные другие итерации в основном тех же самых попыток решения.

2 Ответов

Рейтинг:
6

Mike Meinz

Я решил свою проблему.

Я изменил определение класса, добавив a общественная собственность для имени и RecordDateTime.

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


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)


Чтобы диагностировать эту проблему, я добавил объявления трассировки в XAML для столбцов ListView. В одном из сообщений трассировки говорилось, что Средство доступа был нулевым. Я догадался, что это означает, что я должен был явно объявить a общественная собственность для имени и RecordDateTime. Вывод состоит в том, что механизм привязки не может найти значения в объекте класса, если свойства не объявлены явно.
<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>


Рейтинг:
16

Graeme_Grant

Сообщение об ошибке является лучшим индикатором того, что требуется.

Привязка данных WPF[^] намного превосходит и проще систему привязки данных WinForm. Как только вы потратите время на изучение того, как привязка данных работает в WPF, вам не захочется оглядываться назад.

Вот пример, который быстро покажет вам, как работает привязка данных:

Привязка WPF для обновления свойств требует PropertyChanged событие, которое будет запущено для каждого свойства. Для коллекций мы можем использовать специализированный класс коллекций, называемый ObservableCollection который стреляет а CollectionChanged событие.

Для свойств ниже приведен базовый класс, упрощающий повторяющийся код, необходимый для каждого свойства в классе:

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

Для этого примера я буду использовать PersonModel класс, который наследует ObservableBase базовый класс выше:
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

Теперь мы можем создать нашу коллекцию и добавить событие для демонстрации системы привязки в действии:
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

На приведенной выше странице с кодовым кодом я установил форму DataContext к самой кодовой странице. Это позволит пользовательскому интерфейсу (XAML) видеть уведомления о привязке и обновлять отображение.

Наконец, мы можем связывать то свойства из кода-позади к элементам пользовательского интерфейса:
<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>

В приведенном выше примере используется элемент управления ListBox. Принципы для ListView точно такие же. Вот пользовательский интерфейс, использующий вместо этого ListView:
<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>

В этом примере используется тот же метод привязки данных, что и в шаблоне проектирования MVVM (Model View ViewModel), за исключением того, что ViewModel находится в коде позади него. MVVM немного более вовлечен и выходит за рамки этого ответа, но стоит потратить время на обучение.


Graeme_Grant

Привет Майк,

Он фактически отвечает на ваш вопрос и следующий, который вы зададите, когда измените данные свойства.

С помощью WPF вы работаете с данными, и пользовательский интерфейс будет отражать изменения. Ваш код работает непосредственно с элементами управления, а не с данными. Когда вы работаете с данными, это намного проще и не связано с конкретными элементами управления. Вышеприведенное решение демонстрирует это.

Но чтобы ответить на ваш конкретный вопрос, чтобы добавить новую запись, вы просто создаете новый класс записей и добавляете его в класс записей. ObservableCollection и пользовательский интерфейс будет отражать это изменение - в этом случае будет добавлена новая строка:

Persons.Add(New PersonModel With {
    .Name = String.Format("Person {0}", i),
    .Age = rand.[Next](20, 50)
})

Mike Meinz

Спасибо. Ваше решение очень помогло. Есть так много частичных примеров. Хорошо иметь возможность увидеть полный рабочий пример.

Graeme_Grant

Мы всегда рады вам :)