ОТКАЗ
Этот вопрос относится к заданию на курс программирования, который я делаю. Ваша помощь напрямую повлияет на мои оценки за это задание.
Фон
- Задача - написать программу, которая поможет в управлении ветеринарной клиникой.
- Мы получили устаревшую базу данных 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.
Соответствующая часть экрана:
Описание проблемы
Я пытаюсь обнаружить изменения в одной ячейке, а затем активировать кнопку «Сохранить», чтобы указать, что содержимое сетки было обновлено, но не сохранено. В моей виртуальной машине у меня есть метод 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
Если кто-то может предложить какое-то руководство или помощь в решении этой проблемы, я был бы очень признателен.
Заранее спасибо.