Imports System.ComponentModel
Imports System.Collections.ObjectModel
Imports System.ComponentModel.DataAnnotations


''' <summary>
''' Simple data class that implements two important binding interfaces:
''' 
''' INotifyPropertyChanged:
'''   Notifies bound controls of changes to the object properties so they can synchronize
'''   the UI with the current object state.
'''   
''' IEditableObject:
'''   Provides transacted edits by saving the object state when editing starts and 
'''   committing or rolling back when editing ends.
'''   
''' </summary>
Public Class Product
    Implements IDataErrorInfo, INotifyPropertyChanged, IEditableObject
    Private Shared _lines() As String = "Computers|Washers|Stoves".Split("|"c)
    Private Shared _colors() As String = "Red|Green|Blue|White".Split("|"c)

    ' object model
    <Display(Name:="Line")>
    Public Property Line() As String
        Get
            Return CStr(GetValue("Line"))
        End Get
        Set(ByVal value As String)
            SetValue("Line", value)
        End Set
    End Property

    <Display(Name:="Color")>
    Public Property Color() As String
        Get
            Return CStr(GetValue("Color"))
        End Get
        Set(ByVal value As String)
            SetValue("Color", value)
        End Set
    End Property

    <Display(Name:="Name")>
    Public Property Name() As String
        Get
            Return CStr(GetValue("Name"))
        End Get
        Set(ByVal value As String)
            SetValue("Name", value)
        End Set
    End Property

    <Display(Name:="Price")>
    Public Property Price() As Double?
        Get
            Return CType(GetValue("Price"), Double?)
        End Get
        Set(ByVal value As Double?)
            SetValue("Price", value)
        End Set
    End Property

    <Display(Name:="Weight")>
    Public Property Weight() As Double?
        Get
            Return CType(GetValue("Weight"), Double?)
        End Get
        Set(ByVal value As Double?)
            SetValue("Weight", value)
        End Set
    End Property

    <Display(Name:="Cost")>
    Public Property Cost() As Double?
        Get
            Return CType(GetValue("Cost"), Double?)
        End Get
        Set(ByVal value As Double?)
            SetValue("Cost", value)
        End Set
    End Property

    <Display(Name:="Volume")>
    Public Property Volume() As Double?
        Get
            Return CType(GetValue("Volume"), Double?)
        End Get
        Set(ByVal value As Double?)
            SetValue("Volume", value)
        End Set
    End Property

    <Display(Name:="Discontinued")>
    Public Property Discontinued() As Boolean?
        Get
            Return CType(GetValue("Discontinued"), Boolean?)
        End Get
        Set(ByVal value As Boolean?)
            SetValue("Discontinued", value)
        End Set
    End Property

    <Display(Name:="Rating")>
    Public Property Rating() As Integer?
        Get
            Return CType(GetValue("Rating"), Integer?)
        End Get
        Set(ByVal value As Integer?)
            SetValue("Rating", value)
        End Set
    End Property

    ' get/set values
    Private _values As New Dictionary(Of String, Object)()
    Private Function GetValue(ByVal p As String) As Object
        Dim value As Object
        _values.TryGetValue(p, value)
        Return value
    End Function
    Private Sub SetValue(ByVal p As String, ByVal value As Object)
        If Not Object.Equals(value, GetValue(p)) Then
            _values(p) = value
            OnPropertyChanged(p)
        End If
    End Sub
    Protected Overridable Sub OnPropertyChanged(ByVal p As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(p))
    End Sub

    ' factory
    Public Shared Function GetProducts(ByVal count As Integer) As ObservableCollection(Of Product)
        Dim list = New ObservableCollection(Of Product)()
        Dim rnd = New Random(0)
        For i As Integer = 0 To count - 1
            Dim p = New Product()
            p.Line = _lines(rnd.Next() Mod _lines.Length)
            p.Color = _colors(rnd.Next() Mod _colors.Length)
            p.Name = String.Format("{0} {1}{2}", p.Line.Substring(0, p.Line.Length - 1), p.Line.Chars(0), i)
            p.Price = rnd.Next(1, 1000)
            p.Cost = rnd.Next(1, 600)
            p.Weight = rnd.Next(1, 100)
            p.Volume = rnd.Next(500, 5000)
            p.Discontinued = rnd.NextDouble() < 0.1
            p.Rating = rnd.Next(0, 5)
            list.Add(p)
        Next i
        Return list
    End Function
    Public Shared Function GetLines() As String()
        Return _lines
    End Function
    Public Shared Function GetColors() As String()
        Return _colors
    End Function

#Region "INotifyPropertyChanged Members"

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

#End Region

#Region "IEditableObject Members"

    Private Shared _clone As Dictionary(Of String, Object)
    Public Sub BeginEdit() Implements IEditableObject.BeginEdit
        If _clone Is Nothing Then
            _clone = New Dictionary(Of String, Object)()
        End If
        _clone.Clear()
        For Each key In _values.Keys
            _clone(key) = _values(key)
        Next key
    End Sub
    Public Sub CancelEdit() Implements IEditableObject.CancelEdit
        _values.Clear()
        For Each key In _clone.Keys
            _values(key) = _clone(key)
        Next key
        OnPropertyChanged(Nothing)
    End Sub
    Public Sub EndEdit() Implements IEditableObject.EndEdit
    End Sub

#End Region

#Region "IDataErrorInfo members"

    Private ReadOnly Property [Error]() As String Implements IDataErrorInfo.Error
        Get
            Return If(Price < Cost, "Price must be > Cost", Nothing)
        End Get
    End Property
    Public ReadOnly Property IDataErrorInfo_Item(ByVal columnName As String) As String Implements IDataErrorInfo.Item
        Get
            Select Case columnName
                Case "Price"
                    Return If(Price < 0, "Price must be > 0", Nothing)
                Case "Cost"
                    Return If(Cost < 0, "Cost must be > 0", Nothing)
            End Select
            Return Nothing
        End Get
    End Property

#End Region
End Class
