Files

417 lines
13 KiB
C#
Raw Permalink Normal View History

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using FluentValidation;
using PCL.Core.App.Localization;
namespace PCL;
public class MyTextBox : TextBox
{
public delegate void ValidateChangedEventHandler(object sender, EventArgs e);
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius",
typeof(CornerRadius), typeof(MyTextBox), new PropertyMetadata(new CornerRadius(3d)));
public static readonly DependencyProperty ValidateResultProperty = DependencyProperty.Register("ValidateResult",
typeof(string), typeof(MyTextBox),
new PropertyMetadata("",
(d, e) => d.SetValue(IsValidatedPropertyKey,
string.IsNullOrEmpty((string)e.NewValue))));
private static readonly DependencyPropertyKey IsValidatedPropertyKey =
DependencyProperty.RegisterReadOnly("IsValidated", typeof(bool), typeof(MyTextBox), new PropertyMetadata(true));
public static readonly DependencyProperty IsValidatedProperty = IsValidatedPropertyKey.DependencyProperty;
public static readonly DependencyProperty HintTextProperty = DependencyProperty.Register("HintText", typeof(string),
typeof(MyTextBox), new PropertyMetadata("", (t, e) =>
{
var textBox = (MyTextBox)t;
textBox.UpdateHintText();
}));
// 额外控件初始化
public List<RoutedEventHandler> changedEventList = new();
// 提示文本
/// <summary>
/// 是否已经由用户输入过文本,若尚未输入过,则不显示输入检查的失败。
/// </summary>
private bool isTextChanged;
private ValidateState shownValidateResult = ValidateState.NotInited;
// 事件
public int Uuid = ModBase.GetUuid();
public MyTextBox()
{
Loaded += (_, _) => Validate();
TextChanged += (a, b) => MyTextBox_TextChanged((MyTextBox)a, b);
IsEnabledChanged += (_, _) => RefreshColor();
MouseEnter += (_, _) => RefreshColor();
MouseLeave += (_, _) => RefreshColor();
GotFocus += (_, _) => RefreshColor();
LostFocus += (_, _) => RefreshColor();
IsEnabledChanged += (_, _) => RefreshTextColor();
}
// 自定义属性
public bool HasBackground { get; set; } = true;
public bool ShowValidateResult { get; set; } = true;
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
private TextBlock labWrong
{
get
{
if (Template is null) return null;
if (field is null) field = (TextBlock)Template.FindName("labWrong", this);
return field;
}
}
private TextBlock labHint
{
get
{
if (Template is null) return null;
if (field is null) field = (TextBlock)Template.FindName("labHint", this);
return field;
}
}
// 输入验证
/// <summary>
/// 输入验证结果。若为空字符串则无错误,否则为第一个错误原因。
/// </summary>
public string ValidateResult
{
get => (string)GetValue(ValidateResultProperty);
set => SetValue(ValidateResultProperty, value);
}
/// <summary>
/// 是否通过了输入验证。
/// </summary>
public bool IsValidated => (bool)GetValue(IsValidatedProperty);
/// <summary>
/// 输入验证的规则。
/// </summary>
public Collection<IValidator<string>> ValidateRules
{
get => field;
set
{
field = value;
Validate();
}
} = new();
public string HintText
{
get => (string)GetValue(HintTextProperty);
set => SetValue(HintTextProperty, value);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (string.IsNullOrEmpty(HintText) || !string.IsNullOrEmpty(labHint.Text))
return;
UpdateHintText();
}
public event ValidateChangedEventHandler? ValidateChanged;
public event RoutedEventHandler ValidatedTextChanged
{
add => changedEventList.Add(value);
remove => changedEventList.Remove(value);
}
private void OnValidatedTextChanged(object sender, TextChangedEventArgs e)
{
foreach (var handler in changedEventList)
if (handler is not null)
handler.Invoke(sender, e);
}
/// <summary>
/// 进行输入验证。
/// </summary>
public void Validate()
{
var stringResult = GetValidateResult();
ValidateResult = stringResult;
// 根据结果改变样式
if (shownValidateResult != (IsValidated ? ValidateState.Success : ValidateState.FailedAndShowDetail))
{
if (IsLoaded && labWrong is not null)
ChangeValidateResult(IsValidated, true);
else
ModBase.RunInNewThread(() =>
{
Thread.Sleep(30);
ModBase.RunInUi(() => ChangeValidateResult(IsValidated, false));
}, "DelayedValidate Change");
}
// 更新错误信息
if (ShowValidateResult && !IsValidated)
{
if (IsLoaded && labWrong is not null)
labWrong.Text = ValidateResult;
else
ModBase.RunInNewThread(() =>
{
var isFinished = false;
while (!isFinished)
{
Thread.Sleep(20);
ModBase.RunInUiWait(() =>
{
if (labWrong is not null)
{
labWrong.Text = ValidateResult;
isFinished = true;
}
if (!IsLoaded)
isFinished = true;
});
}
}, "DelayedValidate Text");
}
}
/// <summary>
/// 强制显示结果为正常,类似尚未输入过文本的状态。不影响实际的检查结果。
/// </summary>
public void ForceShowAsSuccess()
{
isTextChanged = false;
ChangeValidateResult(IsValidated, true);
}
private void ChangeValidateResult(bool isSuccessful, bool isLoaded)
{
if (isLoaded && ModAnimation.AniControlEnabled == 0 && labWrong is not null)
{
if (isSuccessful || !isTextChanged)
{
// 变为正确
shownValidateResult = isSuccessful ? ValidateState.Success : ValidateState.FailedButTextNotChanged;
ModAnimation.AniStart(
new[]
{
ModAnimation.AaOpacity(labWrong, -labWrong.Opacity, 150),
ModAnimation.AaHeight(labWrong, -labWrong.Height, 150,
ease: new ModAnimation.AniEaseOutFluent()),
ModAnimation.AaCode(() => labWrong.Visibility = Visibility.Collapsed, after: true)
}, "MyTextBox Validate " + Uuid);
}
else if (ShowValidateResult)
{
// 变为错误
shownValidateResult = ValidateState.FailedAndShowDetail;
labWrong.Visibility = Visibility.Visible;
ModAnimation.AniStart(
new[]
{
ModAnimation.AaOpacity(labWrong, 1d - labWrong.Opacity, 150),
ModAnimation.AaHeight(labWrong, 21d - labWrong.Height, 150,
ease: new ModAnimation.AniEaseOutFluent())
}, "MyTextBox Validate " + Uuid);
}
else
{
// 变为错误,但不显示文本
shownValidateResult = ValidateState.FailedAndHideDetail;
}
}
else
{
shownValidateResult = ValidateState.NotLoaded;
}
RefreshColor();
ValidateChanged?.Invoke(this, new EventArgs());
}
private void MyTextBox_TextChanged(MyTextBox sender, TextChangedEventArgs e)
{
try
{
UpdateHintText();
// 改变输入记录
isTextChanged = IsLoaded;
// 进行输入验证
Validate();
if (!IsValidated)
return;
// 改变文本
OnValidatedTextChanged(sender, e);
}
catch (Exception ex)
{
ModBase.Log(
ex,
"进行输入验证时出错",
ModBase.LogLevel.Critical,
userSummary: Lang.Text("Application.Control.Error.OperationFailed"));
}
}
private void UpdateHintText()
{
if (labHint is null)
return;
labHint.Text = string.IsNullOrEmpty(Text) ? HintText : "";
}
private string GetValidateResult()
{
var stringResult = string.Empty;
// 执行输入验证
foreach (var rule in ValidateRules)
{
var result = rule.Validate(Text);
stringResult = result.IsValid ? "" : result.Errors[0].ErrorMessage;
}
return stringResult;
}
// 颜色
private void RefreshColor()
{
try
{
// 不对 ComboBox 从属进行动画
if (TemplatedParent is not null && TemplatedParent is MyComboBox)
return;
// 判断当前颜色
string foreColorName;
string backColorName;
int animationTime;
if (IsEnabled)
{
if (IsValidated || !isTextChanged)
{
if (IsFocused)
{
foreColorName = "ColorBrush3";
backColorName = "ColorBrush7";
animationTime = 10;
}
else if (IsMouseOver)
{
foreColorName = "ColorBrush4";
backColorName = "ColorBrush7";
animationTime = 100;
}
else // 未选中
{
foreColorName = "ColorBrushBg0";
backColorName = "ColorBrushHalfWhite";
animationTime = 100;
}
}
else
{
foreColorName = "ColorBrushRedLight";
backColorName = "ColorBrushRedBack";
animationTime = 200;
}
}
else
{
foreColorName = "ColorBrushGray5";
backColorName = "ColorBrushGray6";
animationTime = 200;
}
if (!HasBackground)
backColorName = "ColorBrushTransparent";
// 触发颜色动画
if (IsLoaded && ModAnimation.AniControlEnabled == 0) // 防止默认属性变更触发动画
{
// 有动画
ModAnimation.AniStart(
new[]
{
ModAnimation.AaColor(this, BorderBrushProperty, foreColorName, animationTime),
ModAnimation.AaColor(this, BackgroundProperty, backColorName, animationTime)
}, "MyTextBox Color " + Uuid);
}
else
{
// 无动画
ModAnimation.AniStop("MyTextBox Color " + Uuid);
SetResourceReference(BorderBrushProperty, foreColorName);
SetResourceReference(BackgroundProperty, backColorName);
}
}
catch (Exception ex)
{
ModBase.Log(ex, "文本框颜色改变出错");
}
}
private void RefreshTextColor()
{
var newColor = IsEnabled ? ThemeManager.colorGray1 : ThemeManager.colorGray4;
if (((SolidColorBrush)Foreground).Color.R == newColor.r)
return;
if (IsLoaded && ModAnimation.AniControlEnabled == 0 && !string.IsNullOrEmpty(Text))
{
// 有动画
ModAnimation.AniStart(
new[]
{
ModAnimation.AaColor(this, ForegroundProperty, IsEnabled ? "ColorBrushGray1" : "ColorBrushGray4",
200)
}, "MyTextBox TextColor " + Uuid);
}
else
{
// 无动画
ModAnimation.AniStop("MyTextBox TextColor " + Uuid);
Foreground = newColor;
}
}
// 在按下回车时触发自定义事件
private void MyTextBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter) ModMain.RaiseCustomEvent(this);
}
private enum ValidateState
{
NotInited,
Success,
FailedButTextNotChanged,
FailedAndShowDetail,
FailedAndHideDetail,
NotLoaded
}
}