Public Class MyTextBox
    Inherits TextBox

    '自定义属性

    Public Property HasBackground As Boolean = True
    Public Property ShowValidateResult As Boolean = True
    Public Property CornerRadius As CornerRadius
        Get
            Return GetValue(CornerRadiusProperty)
        End Get
        Set(value As CornerRadius)
            SetValue(CornerRadiusProperty, value)
        End Set
    End Property
    Public Shared ReadOnly CornerRadiusProperty As DependencyProperty =
        DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(MyTextBox), New PropertyMetadata(New CornerRadius(3)))

    '额外控件初始化

    Private _labWrong As TextBlock = Nothing
    Private ReadOnly Property labWrong As TextBlock
        Get
            If Template Is Nothing Then Return Nothing
            If _labWrong Is Nothing Then _labWrong = Template.FindName("labWrong", Me)
            Return _labWrong
        End Get
    End Property
    Private _labHint As TextBlock = Nothing
    Private ReadOnly Property labHint As TextBlock
        Get
            If Template Is Nothing Then Return Nothing
            If _labHint Is Nothing Then _labHint = Template.FindName("labHint", Me)
            Return _labHint
        End Get
    End Property
    Public Overrides Sub OnApplyTemplate()
        MyBase.OnApplyTemplate()
        If HintText = "" OrElse labHint.Text <> "" Then Return
        labHint.Text = If(Text = "", HintText, "")
    End Sub

    '事件

    Public Uuid As Integer = GetUuid()
    Public Shared Event ValidateChanged(sender As Object, e As EventArgs)
    Public ChangedEventList As New List(Of RoutedEventHandler)
    Public Custom Event ValidatedTextChanged As RoutedEventHandler
        AddHandler(value As RoutedEventHandler)
            ChangedEventList.Add(value)
        End AddHandler
        RemoveHandler(value As RoutedEventHandler)
            ChangedEventList.Remove(value)
        End RemoveHandler
        RaiseEvent(sender As Object, e As TextChangedEventArgs)
            For Each handler As RoutedEventHandler In ChangedEventList
                If Not IsNothing(handler) Then handler.Invoke(sender, e)
            Next
        End RaiseEvent
    End Event

    '输入验证

    ''' <summary>
    ''' 输入验证结果。若为空字符串则无错误，否则为第一个错误原因。
    ''' </summary>
    Public Property ValidateResult As String
        Get
            Return GetValue(ValidateResultProperty)
        End Get
        Set(value As String)
            SetValue(ValidateResultProperty, value)
        End Set
    End Property
    Public Shared ReadOnly ValidateResultProperty As DependencyProperty =
        DependencyProperty.Register("ValidateResult", GetType(String), GetType(MyTextBox), New PropertyMetadata("",
        Sub(d As MyTextBox, e As DependencyPropertyChangedEventArgs)
            d.SetValue(IsValidatedPropertyKey, String.IsNullOrEmpty(e.NewValue))
        End Sub))

    ''' <summary>
    ''' 是否通过了输入验证。
    ''' </summary>
    Public ReadOnly Property IsValidated As Boolean
        Get
            Return GetValue(IsValidatedProperty)
        End Get
    End Property
    Private Shared ReadOnly IsValidatedPropertyKey As DependencyPropertyKey =
        DependencyProperty.RegisterReadOnly("IsValidated", GetType(Boolean), GetType(MyTextBox), New PropertyMetadata(True))
    Public Shared ReadOnly IsValidatedProperty As DependencyProperty = IsValidatedPropertyKey.DependencyProperty

    ''' <summary>
    ''' 输入验证的规则。
    ''' </summary>
    Public Property ValidateRules As ObjectModel.Collection(Of Validate)
        Get
            Return _ValidateRules
        End Get
        Set(Value As ObjectModel.Collection(Of Validate))
            _ValidateRules = Value
            Validate()
        End Set
    End Property
    Private _ValidateRules As New ObjectModel.Collection(Of Validate)
    ''' <summary>
    ''' 进行输入验证。
    ''' </summary>
    Public Sub Validate() Handles Me.Loaded
        '执行输入验证
        ValidateResult = ModValidate.Validate(Text, ValidateRules)
        '根据结果改变样式
        If ShownValidateResult <> If(IsValidated, ValidateState.Success, ValidateState.FailedAndShowDetail) Then
            If IsLoaded AndAlso labWrong IsNot Nothing Then
                ChangeValidateResult(IsValidated, True)
            Else
                RunInNewThread(
                Sub()
                    Thread.Sleep(30)
                    RunInUi(Sub() ChangeValidateResult(IsValidated, False))
                End Sub, "DelayedValidate Change")
            End If
        End If
        '更新错误信息
        If ShowValidateResult AndAlso Not IsValidated Then
            If IsLoaded AndAlso labWrong IsNot Nothing Then
                labWrong.Text = ValidateResult
            Else
                RunInNewThread(
                Sub()
                    Dim IsFinished As Boolean = False
                    Do Until IsFinished
                        Thread.Sleep(20)
                        RunInUiWait(
                        Sub()
                            If labWrong IsNot Nothing Then
                                labWrong.Text = ValidateResult
                                IsFinished = True
                            End If
                            If Not IsLoaded Then IsFinished = True
                        End Sub)
                    Loop
                End Sub, "DelayedValidate Text")
            End If
        End If
    End Sub

    ''' <summary>
    ''' 强制显示结果为正常，类似尚未输入过文本的状态。不影响实际的检查结果。
    ''' </summary>
    Public Sub ForceShowAsSuccess()
        IsTextChanged = False
        ChangeValidateResult(IsValidated, True)
    End Sub
    Private ShownValidateResult As ValidateState = ValidateState.NotInited
    Private Sub ChangeValidateResult(IsSuccessful As Boolean, IsLoaded As Boolean)
        If IsLoaded AndAlso AniControlEnabled = 0 AndAlso labWrong IsNot Nothing Then
            If IsSuccessful OrElse Not IsTextChanged Then
                '变为正确
                ShownValidateResult = If(IsSuccessful, ValidateState.Success, ValidateState.FailedButTextNotChanged)
                AniStart({
                     AaOpacity(labWrong, -labWrong.Opacity, 150),
                     AaHeight(labWrong, -labWrong.Height, 150,, New AniEaseOutFluent),
                     AaCode(Sub() labWrong.Visibility = Visibility.Collapsed,, True)
                }, "MyTextBox Validate " & Uuid)
            ElseIf ShowValidateResult Then
                '变为错误
                ShownValidateResult = ValidateState.FailedAndShowDetail
                labWrong.Visibility = Visibility.Visible
                AniStart({
                     AaOpacity(labWrong, 1 - labWrong.Opacity, 150),
                     AaHeight(labWrong, 21 - labWrong.Height, 150,, New AniEaseOutFluent)
                }, "MyTextBox Validate " & Uuid)
            Else
                '变为错误，但不显示文本
                ShownValidateResult = ValidateState.FailedAndHideDetail
            End If
        Else
            ShownValidateResult = ValidateState.NotLoaded
        End If
        RefreshColor()
        RaiseEvent ValidateChanged(Me, New EventArgs)
    End Sub
    Private Enum ValidateState
        NotInited
        Success
        FailedButTextNotChanged
        FailedAndShowDetail
        FailedAndHideDetail
        NotLoaded
    End Enum

    '提示文本

    ''' <summary>
    ''' 是否已经由用户输入过文本，若尚未输入过，则不显示输入检查的失败。
    ''' </summary>
    Private IsTextChanged As Boolean = False
    Public Property HintText As String
        Get
            Return GetValue(HintTextProperty)
        End Get
        Set(value As String)
            SetValue(HintTextProperty, value)
        End Set
    End Property
    Public Shared ReadOnly HintTextProperty As DependencyProperty =
        DependencyProperty.Register("HintText", GetType(String), GetType(MyTextBox), New PropertyMetadata("",
        Sub(t As MyTextBox, e As DependencyPropertyChangedEventArgs)
            If t.labHint IsNot Nothing Then t.labHint.Text = If(t.Text = "", t.HintText, "")
        End Sub))
    Private Sub MyTextBox_TextChanged(sender As MyTextBox, e As TextChangedEventArgs) Handles Me.TextChanged
        Try
            '改变提示文本
            If labHint IsNot Nothing Then labHint.Text = If(Text = "", HintText, "")
            '改变输入记录
            IsTextChanged = IsLoaded
            '进行输入验证
            Validate()
            If Not IsValidated Then Return
            '改变文本
            RaiseEvent ValidatedTextChanged(sender, e)
        Catch ex As Exception
            Log(ex, "进行输入验证时出错", LogLevel.Critical)
        End Try
    End Sub

    '颜色

    Private Sub RefreshColor() Handles Me.IsEnabledChanged, Me.MouseEnter, Me.MouseLeave, Me.GotFocus, Me.LostFocus
        Try
            '不对 ComboBox 从属进行动画
            If TemplatedParent IsNot Nothing AndAlso TypeOf TemplatedParent Is MyComboBox Then Return
            '判断当前颜色
            Dim ForeColorName As String, BackColorName As String
            Dim AnimationTime As Integer
            If IsEnabled Then
                If IsValidated OrElse Not IsTextChanged Then
                    If IsFocused Then
                        ForeColorName = "ColorBrush3"
                        BackColorName = "ColorBrush7"
                        AnimationTime = 10
                    ElseIf IsMouseOver Then
                        ForeColorName = "ColorBrush4"
                        BackColorName = "ColorBrush7"
                        AnimationTime = 100
                    Else '未选中
                        ForeColorName = "ColorBrushBg0"
                        BackColorName = "ColorBrushHalfWhite"
                        AnimationTime = 100
                    End If
                Else
                    ForeColorName = "ColorBrushRedLight"
                    BackColorName = "ColorBrushRedBack"
                    AnimationTime = 200
                End If
            Else
                ForeColorName = "ColorBrushGray5"
                BackColorName = "ColorBrushGray6"
                AnimationTime = 200
            End If
            If Not HasBackground Then BackColorName = "ColorBrushTransparent"
            '触发颜色动画
            If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
                '有动画
                AniStart({
                         AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime),
                         AaColor(Me, BackgroundProperty, BackColorName, AnimationTime)
                     }, "MyTextBox Color " & Uuid)
            Else
                '无动画
                AniStop("MyTextBox Color " & Uuid)
                SetResourceReference(BorderBrushProperty, ForeColorName)
                SetResourceReference(BackgroundProperty, BackColorName)
            End If

        Catch ex As Exception
            Log(ex, "文本框颜色改变出错")
        End Try
    End Sub
    Private Sub RefreshTextColor() Handles Me.IsEnabledChanged
        Dim NewColor As MyColor = If(IsEnabled, ColorGray1, ColorGray4)
        If CType(Foreground, SolidColorBrush).Color.R = NewColor.R Then Return
        If IsLoaded AndAlso AniControlEnabled = 0 AndAlso Not Text = "" Then
            '有动画
            AniStart({AaColor(Me, ForegroundProperty, If(IsEnabled, "ColorBrushGray1", "ColorBrushGray4"), 200)}, "MyTextBox TextColor " & Uuid)
        Else
            '无动画
            AniStop("MyTextBox TextColor " & Uuid)
            Foreground = NewColor
        End If
    End Sub

End Class
