我试图在WinUI 3上做一个自定义控件,它可以验证用户输入并根据结果对控件进行更改。
通用.xaml:
<Style TargetType="controls:InputValidationHost">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:InputValidationHost">
<StackPanel>
<ContentPresenter Content="{TemplateBinding InputControl}" x:Name="InputControl" />
<ContentPresenter x:Name="CaptionText"
FontSize="{StaticResource CaptionTextFontSize}" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SetStyleByState">
<VisualState x:Name="Unvalidated">
<VisualState.StateTriggers>
<StateTrigger IsActive="{TemplateBinding IsUnvalidated}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="CaptionText.Content"
Value="{TemplateBinding UnvalidatedMessage}" />
<Setter Target="CaptionText.Foreground"
Value="{ThemeResource SystemColorGrayTextColor}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Passed">
<VisualState.StateTriggers>
<StateTrigger IsActive="{TemplateBinding HasPassed}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="CaptionText.Content"
Value="{TemplateBinding PassedMessage}" />
<Setter Target="CaptionText.Foreground" Value="Green" />
<Setter Target="InputControl.Content.BorderBrush" Value="Green" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Error">
<VisualState.StateTriggers>
<StateTrigger IsActive="{TemplateBinding HasError}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="CaptionText.Content"
Value="{TemplateBinding ErrorMessage}" />
<Setter Target="CaptionText.Foreground" Value="Red" />
<Setter Target="InputControl.Content.BorderBrush" Value="Red" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
输入验证主机.cs:
// Copyright (c) Microsoft Corporation and Contributors.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using AkiraVoid.WordBook.Enums;
using AkiraVoid.WordBook.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace AkiraVoid.WordBook.Controls
{
[ContentProperty(Name = "InputControl")]
public sealed class InputValidationHost : Control
{
public InputValidationHost()
{
this.DefaultStyleKey = typeof(InputValidationHost);
}
public Control InputControl
{
get => (Control)GetValue(InputControlProperty);
set => SetValue(InputControlProperty, value);
}
public static readonly DependencyProperty InputControlProperty = DependencyProperty.Register(
nameof(InputControl),
typeof(Control),
typeof(InputValidationHost),
new(null));
public InputValidationState State
{
get => (InputValidationState)GetValue(StateProperty);
set => SetValue(StateProperty, value);
}
public static readonly DependencyProperty StateProperty = DependencyProperty.Register(
nameof(State),
typeof(InputValidationState),
typeof(InputValidationHost),
new(InputValidationState.Unvalidated, StateChangedCallback));
public IList<Func<object, bool>> Validators
{
get => (IList<Func<object, bool>>)GetValue(ValidatorsProperty);
set => SetValue(ValidatorsProperty, value);
}
public static readonly DependencyProperty ValidatorsProperty = DependencyProperty.Register(
nameof(Validators),
typeof(IList<Func<object, bool>>),
typeof(InputValidationHost),
new(new List<Func<object, bool>>()));
public object ErrorMessage
{
get => GetValue(ErrorMessageProperty);
set => SetValue(ErrorMessageProperty, value);
}
public static readonly DependencyProperty ErrorMessageProperty = DependencyProperty.Register(
nameof(ErrorMessage),
typeof(object),
typeof(InputValidationHost),
new(null));
public object PassedMessage
{
get => GetValue(PassedMessageProperty);
set => SetValue(PassedMessageProperty, value);
}
public static readonly DependencyProperty PassedMessageProperty = DependencyProperty.Register(
nameof(PassedMessage),
typeof(object),
typeof(InputValidationHost),
new(null));
public object UnvalidatedMessage
{
get => GetValue(UnvalidatedMessageProperty);
set => SetValue(UnvalidatedMessageProperty, value);
}
public static readonly DependencyProperty UnvalidatedMessageProperty = DependencyProperty.Register(
nameof(UnvalidatedMessage),
typeof(object),
typeof(InputValidationHost),
new(null));
public bool HasPassed
{
get => (bool)GetValue(HasPassedProperty);
set => SetValue(HasPassedProperty, value);
}
public static readonly DependencyProperty HasPassedProperty = DependencyProperty.Register(
nameof(HasPassed),
typeof(bool),
typeof(InputValidationHost),
new(false));
public bool HasError
{
get => (bool)GetValue(HasErrorProperty);
set => SetValue(HasErrorProperty, value);
}
public static readonly DependencyProperty HasErrorProperty = DependencyProperty.Register(
nameof(HasError),
typeof(bool),
typeof(InputValidationHost),
new(false));
public bool IsUnvalidated
{
get => (bool)GetValue(IsUnvalidatedProperty);
set => SetValue(IsUnvalidatedProperty, value);
}
public static readonly DependencyProperty IsUnvalidatedProperty = DependencyProperty.Register(
nameof(IsUnvalidated),
typeof(bool),
typeof(InputValidationHost),
new(true));
public event EventHandler<ValidationEventArgs> Validate;
public event EventHandler<ValidationEventArgs> Validated;
public event EventHandler<ValidationEventArgs> Passed;
public event EventHandler<ValidationEventArgs> Error;
public event EventHandler<ValidationEventArgs> StateChanged;
public Func<Control, object> GetContent { get; set; }
private void OnValidate()
{
Validate?.Invoke(this, new() { State = State });
}
private void OnValidated()
{
Validated?.Invoke(this, new() { State = State });
}
private void OnPassed()
{
Passed?.Invoke(this, new() { State = State });
}
private void OnError()
{
Error?.Invoke(this, new() { State = State });
}
private static void StateChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
var control = (InputValidationHost)d;
control.SetValue(HasPassedProperty, control.State == InputValidationState.Passed);
control.SetValue(HasErrorProperty, control.State == InputValidationState.Error);
control.SetValue(IsUnvalidatedProperty, control.State == InputValidationState.Unvalidated);
control.StateChanged?.Invoke(control, new() { State = control.State });
}
public void TriggerValidation(object content)
{
OnValidate();
var isPassed = Validators.All(validator => validator(content));
if (isPassed)
{
State = InputValidationState.Passed;
OnPassed();
}
else
{
State = InputValidationState.Error;
OnError();
}
OnValidated();
}
public void TriggerValidation()
{
TriggerValidation(GetContent(InputControl));
}
}
}
我的用法:
<controls:InputValidationHost x:Name="InputValidation" ErrorMessage="Error" PassedMessage="Passed" UnvalidatedMessage="Unvalidated">
<TextBox KeyUp="OnEnterPressed" />
</controls:InputValidationHost>
这应该会对依赖属性HasError
,HasPassed
,IsUnvalidated
做出React,并将更改应用到InputControl
和CaptionText
。但是当我使用此控件时,其中包含一个TextBox,得到的只是一个没有CaptionText
的默认TextBox。我确信我已经设置了***Message
,并且状态更改正确。
你可以找到一个reproduction on GitHub。
1条答案
按热度按时间ybzsozfc1#
要更改VisualStates,需要调用
VisualStateManager.GoToState()
方法:更新
我看了一下你的repo,恕我直言,你应该使用
GoToState()
而不是StateTriggers
。IsActive
会在True
时更改VisualState
,但在False
时不会更改VisualState
。所以,我想你最好显式更改VisualStates
。这是一个基本的例子:
通用.xaml
自定义文本框.cs
如果您无论如何都需要使用
StateTriggers
,您可以找到一个很好的示例代码here。