Imports PCL.MyLoading Public Class MyLoading Public Event IsErrorChanged(sender As Object, isError As Boolean) Public Event StateChanged(sender As Object, newState As MyLoadingState, oldState As MyLoadingState) Public Event Click(sender As Object, e As MouseButtonEventArgs) Public Property AutoRun As Boolean = True Private Uuid As Integer = GetUuid() #Region "颜色" Public Property Foreground As SolidColorBrush Get Return GetValue(ForegroundProperty) End Get Set(value As SolidColorBrush) SetValue(ForegroundProperty, value) End Set End Property Public Shared ReadOnly ForegroundProperty As DependencyProperty = DependencyProperty.Register("Foreground", GetType(SolidColorBrush), GetType(MyLoading)) Public Sub New() InitializeComponent() SetResourceReference(ForegroundProperty, "ColorBrush3") End Sub #End Region #Region "文本" Private Property _ShowProgress As Boolean = False Public Property ShowProgress As Boolean Get Return _ShowProgress End Get Set(value As Boolean) If _ShowProgress = value Then Exit Property _ShowProgress = value RefreshText() End Set End Property Private _Text As String = "加载中" Public Property Text As String Get Return _Text End Get Set(ByVal value As String) _Text = value RefreshText() End Set End Property Private _TextError As String = "加载失败" Public Property TextError As String Get Return _TextError End Get Set(ByVal value As String) _TextError = value RefreshText() End Set End Property ''' ''' 是否在使用 Loader 时使用 Loader 的错误输出来替换默认的错误文本显示。 ''' Public Property TextErrorInherit As Boolean = True Private Sub RefreshText() Handles Me.IsErrorChanged, Me.Loaded If InnerState = MyLoadingState.Error Then If TextErrorInherit AndAlso State.IsLoader Then Dim Ex As Exception = CType(State, Object).Error If Ex Is Nothing Then LabText.Text = "未知错误" Else Do While Ex.InnerException IsNot Nothing Ex = Ex.InnerException Loop LabText.Text = StrTrim(Ex.Message) If LabText.Text.Contains("远程主机强迫关闭了一个现有的连接") OrElse LabText.Text.Contains("操作已超时") OrElse LabText.Text.Contains("操作超时") OrElse LabText.Text.Contains("服务器超时") OrElse LabText.Text.Contains("连接超时") Then LabText.Text = "网络环境不佳,请重试或尝试使用 VPN" End If Else LabText.Text = TextError End If Else If ShowProgress AndAlso State.IsLoader Then LabText.Text = Text & " - " & Math.Floor(CType(State, Object).Progress * 100) & "%" Else LabText.Text = Text End If End If End Sub #End Region #Region "状态改变" '状态枚举 Public Enum MyLoadingState Unloaded = -1 Run = 0 [Stop] = 1 [Error] = 2 End Enum '用于外部改变的公开状态 Private WithEvents _State As ILoadingTrigger Public Property State As ILoadingTrigger Get InitState() Return _State End Get Set(value As ILoadingTrigger) _State = value RefreshState() End Set End Property Private Sub InitState() Handles Me.Loaded If _State Is Nothing Then _State = New MyLoadingStateSimulator If AutoRun Then _State.LoadingState = MyLoadingState.Run End If End Sub Private Sub RefreshState() Handles _State.LoadingStateChanged, Me.Loaded, Me.Unloaded If _State.LoadingState = MyLoadingState.Run AndAlso Not IsLoaded Then InnerState = MyLoadingState.Stop InnerState = _State.LoadingState OuterState = _State.LoadingState AniLoop() End Sub '用于引发外部事件的状态 Private Property _OuterState As MyLoadingState = MyLoadingState.Unloaded Private Property OuterState As MyLoadingState Get Return _OuterState End Get Set(value As MyLoadingState) If _OuterState = value Then Exit Property Dim OldValue = _OuterState _OuterState = value '引发事件 RaiseEvent StateChanged(Me, value, OldValue) If (OldValue = MyLoadingState.Error) <> (value = MyLoadingState.Error) Then RaiseEvent IsErrorChanged(Me, value = MyLoadingState.Error) End Set End Property '用于引发内部动画事件的状态 Private Property _InnerState As MyLoadingState = MyLoadingState.Unloaded Private Property InnerState As MyLoadingState Get Return _InnerState End Get Set(value As MyLoadingState) If _InnerState = value Then Exit Property Dim OldValue = _InnerState _InnerState = value '引发事件 AniLoop() If (OldValue = MyLoadingState.Error) <> (value = MyLoadingState.Error) Then ErrorAnimation(Me, value = MyLoadingState.Error) End Set End Property #End Region #Region "动画" ''' ''' 是否需要动画。 ''' Public Property HasAnimation As Boolean = True ''' ''' 主动画循环是否正在运行中。 ''' Private IsLooping As Boolean = False Private Sub AniLoop() '这坨循环代码也是老屎坑了,救救.jpg If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Exit Sub IsLooping = True ErrorAnimationWaiting = True If ShowProgress Then RefreshText() AniStart({ AaRotateTransform(PathPickaxe, -20 - CType(PathPickaxe.RenderTransform, RotateTransform).Angle, 350, 250, New AniEaseInBack(AniEasePower.Weak)), AaCode(AddressOf RefreshText, 100), '这是为了省下一个 Timer 线程…… AaCode(AddressOf RefreshText, 200), AaCode(AddressOf RefreshText, 300), AaCode(AddressOf RefreshText, 400), AaCode(AddressOf RefreshText, 500), AaRotateTransform(PathPickaxe, 50, 900,, New AniEaseOutFluent, True), AaRotateTransform(PathPickaxe, 25, 900,, New AniEaseOutElastic(AniEasePower.Weak)), AaCode(Sub() PathLeft.Opacity = 1 PathLeft.Margin = New Thickness(7, 41, 0, 0) PathRight.Opacity = 1 PathRight.Margin = New Thickness(14, 41, 0, 0) ErrorAnimationWaiting = False RefreshText() End Sub), AaCode(AddressOf RefreshText, 100), AaCode(AddressOf RefreshText, 200), AaCode(AddressOf RefreshText, 300), AaCode(AddressOf RefreshText, 400), AaCode(AddressOf RefreshText, 500), AaCode(AddressOf RefreshText, 600), AaCode(AddressOf RefreshText, 700), AaCode(AddressOf RefreshText, 800), AaOpacity(PathLeft, -1, 100, 70), AaX(PathLeft, -5, 180, 20, New AniEaseOutFluent), AaY(PathLeft, -6, 180, 20, New AniEaseOutFluent), AaOpacity(PathRight, -1, 100, 70), AaX(PathRight, 5, 180, 20, New AniEaseOutFluent), AaY(PathRight, -6, 180, 20, New AniEaseOutFluent), AaCode(Sub() IsLooping = False AniLoop() End Sub,, True) }, "MyLoader Loop " & Uuid & "/" & GetUuid()) Else AniStart({ AaRotateTransform(PathPickaxe, -20 - CType(PathPickaxe.RenderTransform, RotateTransform).Angle, 350, 250, New AniEaseInBack(AniEasePower.Weak)), AaRotateTransform(PathPickaxe, 50, 900,, New AniEaseOutFluent, True), AaRotateTransform(PathPickaxe, 25, 900,, New AniEaseOutElastic(AniEasePower.Weak)), AaCode(Sub() PathLeft.Opacity = 1 PathLeft.Margin = New Thickness(7, 41, 0, 0) PathRight.Opacity = 1 PathRight.Margin = New Thickness(14, 41, 0, 0) ErrorAnimationWaiting = False End Sub), AaOpacity(PathLeft, -1, 100, 50), AaX(PathLeft, -5, 180,, New AniEaseOutFluent), AaY(PathLeft, -6, 180,, New AniEaseOutFluent), AaOpacity(PathRight, -1, 100, 50), AaX(PathRight, 5, 180,, New AniEaseOutFluent), AaY(PathRight, -6, 180,, New AniEaseOutFluent), AaCode(Sub() IsLooping = False AniLoop() End Sub,, True) }, "MyLoader Loop " & Uuid & "/" & GetUuid()) End If End Sub ''' ''' 镐子是否还没挥下去,要求错误动画等待。 ''' Private ErrorAnimationWaiting As Boolean = False Private Sub ErrorAnimation(sender As Object, isError As Boolean) If isError Then '非错误变为错误 Dim Wait As Integer = If(ErrorAnimationWaiting, 400, 0) AniStart({ AaColor(PanBack, ForegroundProperty, "ColorBrushRedLight", 300), AaOpacity(PathError, 1 - PathError.Opacity, 100, 300 + Wait), AaScaleTransform(PathError, 1 - CType(PathError.RenderTransform, ScaleTransform).ScaleX, 400, 300 + Wait, New AniEaseOutBack) }, "MyLoader Error " & Uuid) Else '错误变为非错误 AniStart({ AaOpacity(PathError, -PathError.Opacity, 100), AaScaleTransform(PathError, 0.5 - CType(PathError.RenderTransform, ScaleTransform).ScaleX, 200), AaColor(PanBack, ForegroundProperty, "ColorBrush3", 300) }, "MyLoader Error " & Uuid) End If End Sub #End Region #Region "点击事件" Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp RaiseEvent Click(sender, e) End Sub Private IsMouseDown As Boolean = False Private Sub Button_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行) IsMouseDown = True End Sub Private Sub Button_MouseLeave(sender As Object, e As Object) Handles Me.MouseLeave, Me.MouseLeftButtonUp IsMouseDown = False End Sub #End Region End Class Public Interface ILoadingTrigger ReadOnly Property IsLoader As Boolean Property LoadingState As MyLoadingState Event LoadingStateChanged(NewState As MyLoadingState, OldState As MyLoadingState) End Interface Public Class MyLoadingStateSimulator Implements ILoadingTrigger Private Property _LoadingState As MyLoadingState = MyLoadingState.Unloaded Public Property LoadingState As MyLoadingState Implements ILoadingTrigger.LoadingState Get Return _LoadingState End Get Set(value As MyLoadingState) If _LoadingState = value Then Exit Property Dim OldState = _LoadingState _LoadingState = value RaiseEvent LoadingStateChanged(value, OldState) End Set End Property Public ReadOnly Property IsLoader As Boolean = False Implements ILoadingTrigger.IsLoader Public Event LoadingStateChanged(NewState As MyLoadingState, OldState As MyLoadingState) Implements ILoadingTrigger.LoadingStateChanged End Class