Как обнаружить изменения в отдельной ячейке сетки данных WPF, связанной с Caliburn.Micro BindableCollection? - PullRequest
0 голосов
/ 08 апреля 2019

ОТКАЗ Этот вопрос относится к заданию на курс программирования, который я делаю. Ваша помощь напрямую повлияет на мои оценки за это задание.

Фон

  • Задача - написать программу, которая поможет в управлении ветеринарной клиникой.
  • Мы получили устаревшую базу данных MySQL и не можем изменить схема.
  • Назначение требует, чтобы приложение было записано в VB
  • Я использую Caliburn.Micro в качестве основы MVVM и пишу это как приложение WPF
  • Я использую Entity Framework для доступа к данным, чтобы воспользоваться встроенным отслеживанием изменений
  • Предоставлены макеты экрана, и они ДОЛЖНЫ дублироваться в назначении. На экране «Управление питомцами» информация должна быть представлена ​​в сетке данных, и пользователь должен иметь возможность редактировать запись из этой сетки.

Текущая настройка приложения

Я использовал Entity Framework Code First из рабочего процесса базы данных, чтобы сгенерировать классы, которые будут представлены как DBSets в контексте EF. Для класса «pet», сгенерированного в DAL, я создал соответствующий класс в своем пользовательском интерфейсе, который дублирует все свойства «pet», но наследуется от «Screen» Caliburn.Micro и реализует NotifyOfPropertyChange () для каждого свойства «editable» .

Я также создал интерфейс IPet, который реализуется как сгенерированным классом «pet» (через созданный мной частичный класс), так и классом UIPet.

Соответствующая часть экрана: Datagrid from Manage Pet Information screen

Описание проблемы

Я пытаюсь обнаружить изменения в одной ячейке, а затем активировать кнопку «Сохранить», чтобы указать, что содержимое сетки было обновлено, но не сохранено. В моей виртуальной машине у меня есть метод Save (), который просто вызывает SaveChanges (). Я реализовал CanSave как свойство только для чтения:

 Public ReadOnly Property CanSave() As Boolean
    Get
        Return pm.PetListChanged()
    End Get
End Property

При изменении ячейки в сетке данных кнопка «Сохранить» не активируется (на снимке экрана выше я установил свойство CanChange, чтобы оно всегда возвращало значение True).

Если, однако, после внесения изменений я нажму кнопку «Обновить», кнопка «Сохранить» активируется. (То же самое относится и к кнопке «Отмена», но я предполагаю, что одинаковое исправление будет работать для обеих кнопок, поскольку они должны активироваться по одной и той же причине).

Нажатие на кнопку Сохранить успешно записывает изменения в базу данных.

Модель, которую я использую для привязки к этой сетке данных:

Imports Caliburn.Micro
Imports TickedOff.DAL

Public Class PetUIModel
    Inherits Screen
    Implements IPet

    Private _customerID As Integer?
    Private _weight As Single?
    Private _gender As String
    Private _DOB As Date?
    Private _breed As String
    Private _species As String
    Private _petName As String
    Private _petID As Integer

    Public Sub New()
        bookings = New HashSet(Of Booking)()
        stays = New HashSet(Of Stay)()
    End Sub

    Public Property petID As Integer Implements IPet.petID
        Get
            Return _petID
        End Get
        Set(value As Integer)
            _petID = value
            NotifyOfPropertyChange(Function() petID)

        End Set
    End Property

    Public Property petName As String Implements IPet.petName
        Get
            Return _petName
        End Get
        Set
            _petName = Value
            NotifyOfPropertyChange(Function() petName)
        End Set
    End Property

    Public Property species As String Implements IPet.species
        Get
            Return _species
        End Get
        Set
            _species = Value
            NotifyOfPropertyChange(Function() species)

        End Set
    End Property

    Public Property breed As String Implements IPet.breed
        Get
            Return _breed
        End Get
        Set
            _breed = Value
            NotifyOfPropertyChange(Function() breed)

        End Set
    End Property

    Public Property DOB As Date? Implements IPet.DOB
        Get
            Return _DOB
        End Get
        Set
            _DOB = Value
            NotifyOfPropertyChange(Function() DOB)

        End Set
    End Property

    Public Property gender As String Implements IPet.gender
        Get
            Return _gender
        End Get
        Set
            _gender = Value
            NotifyOfPropertyChange(Function() gender)

        End Set
    End Property

    Public Property weight As Single? Implements IPet.weight
        Get
            Return _weight
        End Get
        Set
            _weight = Value
            NotifyOfPropertyChange(Function() weight)

        End Set
    End Property

    Public Property customerID As Integer? Implements IPet.customerID
        Get
            Return _customerID
        End Get
        Set
            _customerID = Value
            NotifyOfPropertyChange(Function() customerID)

        End Set
    End Property

    Public Overridable Property customer As customer Implements IPet.customer
    Public Overridable Property bookings As ICollection(Of Booking) Implements IPet.bookings
    Public Overridable Property stays As ICollection(Of Stay) Implements IPet.stays

    Public ReadOnly Property OwnerName() As String Implements IPet.OwnerName
        Get
            Return $"{Me.customer.lastName}, {Me.customer.firstName}"
        End Get
    End Property


End Class

Код для моей ViewModel:

Imports System.Collections.ObjectModel
Imports System.Data
Imports System.Text
Imports System.Windows.Forms
Imports Caliburn.Micro
Imports TickedOff.DAL

Public Class ManagePetInfoViewModel
    Inherits Conductor(Of Object)
    Implements IHandle(Of IPet)

    Private pm As PetManager = New PetManager()
    Private _uipetList As BindableCollection(Of IPet)
    Private _selectedPet As Object 
    Private _petListPanelIsVisible As Boolean = True
    Private _addEditPanelIsVisible As Boolean = False

    Public Sub New()

        EventAggrigationProvider.TickedOffEventAggregator.Subscribe(Me)
        UIPetList = New BindableCollection(Of IPet)(pm.GetPetList)
    End Sub

    Public Property UIPetList() As BindableCollection(Of IPet)
        Get
            Return _uipetList
        End Get
        Set(ByVal value As BindableCollection(Of IPet))
            _uipetList = value

            NotifyOfPropertyChange(Function() UIPetList)
            NotifyOfPropertyChange(Function() CanDelete)
            NotifyOfPropertyChange(Function() CanSave)
            NotifyOfPropertyChange(Function() CanCancel)
        End Set
    End Property

    Public Property SelectedPet() As Object
        Get
            Return _selectedPet
        End Get
        Set(ByVal value As Object)
            _selectedPet = value
            NotifyOfPropertyChange(Function() SelectedPet)
            NotifyOfPropertyChange(Function() CanDelete)
            NotifyOfPropertyChange(Function() UIPetList)
        End Set
    End Property

    Public Property PetListPanelIsVisible() As Boolean
        Get
            Return _petListPanelIsVisible
        End Get
        Set(ByVal value As Boolean)
            _petListPanelIsVisible = value
            NotifyOfPropertyChange(Function() PetListPanelIsVisible)
            NotifyOfPropertyChange(Function() CanSave)
            NotifyOfPropertyChange(Function() UIPetList)
        End Set
    End Property

    Public Property AddEditPanelIsVisible() As Boolean
        Get
            Return _addEditPanelIsVisible
        End Get
        Set(ByVal value As Boolean)
            _addEditPanelIsVisible = value
            NotifyOfPropertyChange(Function() AddEditPanelIsVisible)
            NotifyOfPropertyChange(Function() CanSave)
            NotifyOfPropertyChange(Function() UIPetList)
        End Set
    End Property

    Public ReadOnly Property CanCancel() As Boolean
        Get
            Return pm.PetListChanged()
        End Get
    End Property

    Public ReadOnly Property CanDelete() As Boolean
        Get
            If SelectedPet IsNot Nothing Then
                Return True
            Else
                Return False
            End If
        End Get
    End Property

    Public ReadOnly Property CanSave() As Boolean
        Get
            Return pm.PetListChanged()
            'Return True
        End Get
    End Property

    Public Sub Save()
        pm.Save()
        MessageBox.Show($"Your information has been saved.", "Save Complete", MessageBoxButtons.OK, MessageBoxIcon.Information)
    End Sub

    Public Sub Cancel()
        Dim confirmCancel As DialogResult = MessageBox.Show("Are you sure you want to cancel your changes and reload the information?", "Confirm Cancel", MessageBoxButtons.YesNo)

        If confirmCancel = DialogResult.Yes Then

            'pm = New PetManager()

        End If

    End Sub

    Public Sub Add()
        ActivateItem(New AddEditPetViewModel)
        PetListPanelIsVisible = False
        AddEditPanelIsVisible = True
    End Sub

    Public Sub Delete()
        Dim confirmDelete As DialogResult = MessageBox.Show("Are you sure you want to delete the selected record?", "Confirm Delete Record", MessageBoxButtons.YesNo)

        If confirmDelete = DialogResult.Yes Then
            ' Delete the record
            UIPetList.Remove(SelectedPet)
            NotifyOfPropertyChange(Function() UIPetList)

            MessageBox.Show($"Pet record has been deleted.", "Record Deleted", MessageBoxButtons.OK, MessageBoxIcon.Information)
        End If
    End Sub

    Private Sub ManagePetInfoViewModel_Deactivated(sender As Object, e As DeactivationEventArgs) Handles Me.Deactivated

        If pm.PetListChanged Then

            Dim saveChangesDialog As DialogResult = MessageBox.Show("You have unsaved changes. Would you like to save them now?", "Save Changes", MessageBoxButtons.YesNo)

            If saveChangesDialog = DialogResult.Yes Then
                pm.Save()
            End If
        End If
    End Sub

    Private Sub RefreshData()
        Refresh()
    End Sub

    Public Sub Handle(message As IPet) Implements IHandle(Of IPet).Handle
        message.petID = pm.GetNextPetID()
        message.customer = pm.GetCustomerByID(message.customerID)

        'Add the newly created Pet to the list of pets in the context (RAM)
        pm.AddPet(message)
        NotifyOfPropertyChange(Function() UIPetList)

        'Hide the Add Pet screen, and go back to the Pet Info screen
        PetListPanelIsVisible = True
        AddEditPanelIsVisible = False
    End Sub
    End Class

Код для упомянутого класса PetManager:

Imports System.Collections.ObjectModel
Imports System.Data.Entity
Imports System.Data.Entity.Infrastructure
Imports System.Windows.Forms
Imports TickedOff.DAL
Imports Caliburn.Micro

Public Class PetManager

    Private _db As TickedOffContext = New TickedOffContext()
    Private _PetList As IEnumerable(Of IPet)

    Public Sub New()
        PetList = GetPetList()
    End Sub

    Public Property PetList() As IEnumerable(Of IPet)
        Get
            Return _PetList
        End Get
        Set
            _PetList = Value
        End Set
    End Property

    Public Function GetPetList() As IEnumerable(Of IPet)
        Return _db.Pets.ToList
    End Function

    Public Function PetListChanged() As Boolean
        Return _db.ChangeTracker.HasChanges()
    End Function

    Public Function GetOwnersList() As List(Of customer)
        Return _db.Customers.ToList()
    End Function

    ''' <summary>
    ''' Returns the next available PetID to be used.
    '''
    ''' NOTE:
    ''' This function is only required because the legacy database (provided)
    ''' does NOT have the Auto-Identity attribute in the PetID field set. The database
    ''' cannot easily be changed (legacy databases should not be changed anyway)
    ''' to include the auti-increment attribute.
    ''' </summary>
    Public Function GetNextPetID() As Integer

        Dim output As Integer

        ' Get the last petID from the context
        Dim lastpet As pet = PetList.Last()

        'Increment the petID field
        output = lastpet.petID + 1

        Return output
    End Function

    Public Function GetCustomerByID(custId As Integer) As customer
        Dim output As customer
        Dim q = From c As customer In _db.Customers
                Where c.customerID = custId
                Select c
        output = q.FirstOrDefault()
        Return output
    End Function

    Public Sub AddPet(p As IPet)
        Dim newpet As pet = p
        _db.Pets.Add(p)
        MessageBox.Show("Pet Added to Context")
    End Sub

    Public Sub Save()
        _db.SaveChanges()
    End Sub

    Public Sub Delete(pet As IPet)
        _db.Pets.Remove(pet)
    End Sub

End Class

И, наконец, XAML для сетки данных, которую я использую:

<!-- Pet Management Grid -->
            <DataGrid x:Name="UIPetList"
                  Margin="15 0 15 0"
                  SelectedItem="{Binding Path=SelectedPet, Mode=TwoWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}"
                  AlternatingRowBackground="AliceBlue"
                  AutoGenerateColumns="False"
                  MaxHeight="250"
                  CanUserResizeColumns="False"
                  CanUserResizeRows="False"
                  CanUserReorderColumns="False"
                  SelectionMode="Single"
                  IsSynchronizedWithCurrentItem="True">

                <DataGrid.Columns>
                    <DataGridTextColumn Header="ID"
                                    Binding="{Binding PetID, Mode=OneWay}"
                                    Width="40" />
                    <DataGridTextColumn Header="Name"
                                    Binding="{Binding petName, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Width="150" />
                    <DataGridTextColumn Header="Species"
                                    Binding="{Binding species, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Width="75" />
                    <DataGridTextColumn Header="Breed"
                                    Binding="{Binding breed, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Width="130" />
                    <DataGridTextColumn Header="DOB"
                                    Binding="{Binding DOB, StringFormat={}{0:dd/MM/yyyy}, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Width="100" />
                    <DataGridTextColumn Header="Gender"
                                    Binding="{Binding gender, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Width="60">
                        <!-- Centre the Gender in the column -->
                        <DataGridTextColumn.ElementStyle>
                            <Style TargetType="TextBlock">
                                <Setter Property="HorizontalAlignment" Value="Center" />
                            </Style>
                        </DataGridTextColumn.ElementStyle>
                    </DataGridTextColumn>
                    <DataGridTextColumn Header="Weight"
                                    Binding="{Binding weight, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
                                    Width="60">
                        <!-- Centre the Weight in the column -->
                        <DataGridTextColumn.ElementStyle>
                            <Style TargetType="TextBlock">
                                <Setter Property="HorizontalAlignment" Value="Center" />
                            </Style>
                        </DataGridTextColumn.ElementStyle>
                    </DataGridTextColumn>
                    <DataGridTextColumn Header="Customer"
                                    Binding="{Binding Path=OwnerName, Mode=OneWay, UpdateSourceTrigger=LostFocus}"
                                    Width="*" />
                </DataGrid.Columns>
            </DataGrid>

            <!-- Control Buttons -->
            <Grid Margin="30">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width=" 1*" />
                    <ColumnDefinition Width=" 1*" />
                    <ColumnDefinition Width=" 1*" />
                    <ColumnDefinition Width=" 1*" />
                    <ColumnDefinition Width=" 1*" />
                </Grid.ColumnDefinitions>
                <Button x:Name="Save"
                    Grid.Column="0"
                    Content="Save"
                    ToolTip="Save changes to the database"
                    Margin="5"
                    Padding="5" />

                <Button x:Name="Add"
                    Grid.Column="1"
                    Content="Add"
                    ToolTip="Add a new pet to the database"
                    Margin="5"
                    Padding="5" />
                <Button x:Name="Delete"
                    Grid.Column="2"
                    Content="Delete"
                    ToolTip="Delete selected record from the database"
                    Margin="5"
                    Padding="5" />
                <Button x:Name="Cancel"
                    Grid.Column="3"
                    Content="Cancel"
                    ToolTip="Cancel all pending changes."
                    Margin="5"
                    Padding="5" />
                <Button x:Name="Refresh"
                    Grid.Column="4"
                    Content="Refresh"
                    ToolTip="Refresh the pet list from the database, erasing any unsaved changes."
                    Margin="5"
                    Padding="5" />
            </Grid>

Я ссылался и пытался исправить в следующих статьях. Все они не увенчались успехом, поэтому я в основном удалил или закомментировал предложенный код.

WPF DataGrid - фиксация изменений по ячейкам

ObservableCollection не замечает, когда элемент в нем изменяется (даже с INotifyPropertyChanged)

Событие автоматической смены ячейки DataGrid WPF при привязке к ItemSource

Caliburn.Micro и DataGrid: надежный подход для обнаружения изменений отдельных ячеек?

WPF DataGrid, привязанный к платформе сущностей, не обновляется после изменения данных во ViewModel

Если кто-то может предложить какое-то руководство или помощь в решении этой проблемы, я был бы очень признателен.

Заранее спасибо.

...