Проверка WPF в зависимости от требуемого/не требуемого поля
Я новичок в разработке WPF, но я думал о том, как убить 3-х птиц одним камнем.
Пример: у меня есть форма с 2 TextBox и 2 TextBlocks.
Первой "птицей" было бы "обогатить" некоторый текстовый блок звездочкой, если они относятся к обязательным полям:
<TextBlock Grid.Row="0" Grid.Column="0" Text="Age" customProperty="Required" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Foot Size/>
Затем TextBlocks будет показывать свой текст по-разному, первый будет иметь звездочку, в то время как тот, у которого не определено customproperty, не будет.
Вторая птица должна иметь некоторую проверку правильности значения текстового поля, которое, если я правильно понял, было выполнено с помощью CustomValidationRule, для которого я реализовал класс:
class AgeController: ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value == null)
return new ValidationResult(false, "Null value");
int temp = 1;
Boolean noIllegalChars = int.TryParse(value.ToString(), out temp);
if (temp >= 1)
return new ValidationResult(true, null);
else
return new ValidationResult(false, "Correggi");
}
}
Добавив это в код textBlox XAML:
<TextBox.Text>
<Binding Path="blabla" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:AgeController ValidationStep="RawProposedValue" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
И это работает, но процесс проверки должен отличаться для требуемых и необязательных полей: если требуется, чтобы пустой ввод недействителен, но если он необязательный, пустое поле в порядке.
Как достичь этого, не указав два разных ValidationRule, ссылаясь на текстовый блок, связанный с текстовым полем?
/TL;DR: Я пытаюсь найти способ обогатить текстовый блок атрибутом, который добавляет стиль к его тексту (звездочка или что-то, что хочет клиент), я изменяю, как обогащение изменяет текст только в одном месте) проверка правильности текстового поля, относящаяся к обогащенному текстовому блоку, будет вести себя по-разному на основе значения обогащения.
Надеюсь, я не испортил объяснения.
Ответы
Ответ 1
1. TextBlock не имеет свойства ControlTemplate. Поэтому требуемый (*) не может быть добавлен в TextBlock
Ярлык имеет контрольную таблицу и может фокусироваться на поле ввода. Позвольте использовать его.
- Изменить -
Использование свойства Target для передачи фокуса в TextBox при нажатии Alt + F:
<!-- Prefixing Firstname with _ allows the user to give focus
to the textbox (Target) by pressing Alt + F-->
<local:LabelWithRequiredInfo Content="_Firstname"
IsRequired="false"
Target="{Binding ElementName=textboxFirstname,
Mode=OneWay}" ... />
- Конец редактирования -
Создание подкласса Label: LabelWithRequiredInfo, поэтому можно добавить свойство IsRequired.
(Используйте VS Добавить новый элемент/Пользовательский контроль WPF).
2. Создание свойства зависимой IsRequired для элемента управления, так что привязка будет работать - нам это нужно!
public class LabelWithRequiredInfo : Label
{
public bool IsRequired
{
get { return (bool)GetValue(IsRequiredProperty); }
set { SetValue(IsRequiredProperty, value); }
}
// Using a DependencyProperty as the backing store for IsRequired. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsRequiredProperty =
DependencyProperty.Register("IsRequired", typeof(bool), typeof(LabelWithRequiredInfo), new PropertyMetadata(false));
static LabelWithRequiredInfo()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LabelWithRequiredInfo), new FrameworkPropertyMetadata(typeof(LabelWithRequiredInfo)));
}
}
3. Позвольте заполнить шаблон LabelWithRequiredInfo в Themes\Generic.xaml
(Но шаблон сначала разработан в MainWindow.xaml, щелкнув по метке /Edit template/Copy - чтобы он мог визуализироваться - затем содержимое шаблона копируется в Generic.xaml)
<Style TargetType="{x:Type local:LabelWithRequiredInfo}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:LabelWithRequiredInfo}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<!-- A grid has been added to the template content to have multiple content. -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<!-- The Visibility property has to be converted because it not a bool but has a Visibility type
The converter (pretty classical) can be found in the attached solution, and is declared in the resource section
The binding is made on a property of the component : IsRequired
-->
<TextBlock Text="(*)"
Visibility="{TemplateBinding IsRequired,Converter={StaticResource booleanToVisibilityConverter}}"
Foreground="Red"
Grid.Column="1"
Margin="5 0"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
4. Объявление преобразователя в Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TextboxRequiredMandatoryInput">
<local:BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"/>
5. Декларация ValidationRule с учетом поведения IsRequired:
class RequiredValidationRule : ValidationRule
{
public bool IsRequired { get; set; }
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
var content = value as String;
if (content != null)
{
if (IsRequired && String.IsNullOrWhiteSpace(content))
return new ValidationResult(false, "Required content");
}
return ValidationResult.ValidResult;
}
}
6. И используйте его в своей привязке, как вы нашли:
<TextBox x:Name="textboxFirstname" HorizontalAlignment="Left" Height="23" Margin="236,94,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120">
<TextBox.Text>
<Binding Path="Firstname" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:RequiredValidationRule IsRequired="true" ValidationStep="RawProposedValue" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Здесь вы найдете полное решение:
http://1drv.ms/1igpsyb
Лучшее кодирование
Ответ 2
Для повторного использования и того, как вы описали это требование, может потребоваться агрегатный контроль. Я думаю, что UserControl + some DependencyProperty идеально подходит для этого.
Для моего UserControl мне бы понравилось это.
<UserControl x:Class="WpfApplication1.InputFieldControl"
x:Name="InputUserCtrl"
... >
<StackPanel x:Name="MainPanel">
<TextBlock x:Name="Label">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}{1}:">
<Binding Path="InputLabel" ElementName="InputUserCtrl"/>
<Binding Path="RequiredStringSymbol" ElementName="InputUserCtrl"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBox x:Name="Value" Text="{Binding DataContext, ElementName=InputUserCtrl}"/>
</StackPanel>
Затем на его частичном классе (я добавил количество свойств, обратитесь к части использования):
public partial class InputFieldControl : UserControl
{
// Required property
public static readonly DependencyProperty RequiredProperty =
DependencyProperty.Register("Required",
typeof(bool), typeof(InputFieldControl),
new PropertyMetadata(true, OnRequiredChanged));
private static void OnRequiredChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){
InputFieldControl ctrl = d as InputFieldControl;
// symbol is voided
if ((bool)e.NewValue == false)
ctrl.RequiredStringSymbol = string.Empty;
}
public bool Required {
get { return (bool)GetValue(RequiredProperty); }
set { SetValue(RequiredProperty, value); }
}
// Required string symbol
public static readonly DependencyProperty RequiredStringSymbolProperty =
DependencyProperty.Register("RequiredStringSymbol",
typeof(string), typeof(InputFieldControl),
new PropertyMetadata("*"));
public string RequiredStringSymbol{
get { return (string)GetValue(RequiredStringSymbolProperty); }
set { SetValue(RequiredStringSymbolProperty, value); }
}
// Input Label
public static readonly DependencyProperty InputLabelProperty =
DependencyProperty.Register("InputLabel",
typeof(string), typeof(InputFieldControl),
new PropertyMetadata(string.Empty));
public string InputLabel{
get { return (string)GetValue(InputLabelProperty); }
set { SetValue(InputLabelProperty, value); }
}
И я могу использовать элемент управления следующим образом:
<StackPanel>
<customCtrl:InputFieldControl Required="True"
RequiredStringSymbol="+"
InputLabel="RequiredField"/>
<customCtrl:InputFieldControl Required="False"
InputLabel="NormalField"/>
<customCtrl:InputFieldControl Required="True"
RequiredStringSymbol="*"
InputLabel="AnotherRequiredField">
</customCtrl:InputFieldControl>
</StackPanel>
Что касается части проверки, я бы предпочел использовать IDataErrorInfo. Это может идти рука об руку с ViewModel, так как теперь мы можем привязать свойство Required.
Ответ 3
Первая точка. Вы можете определить настраиваемый шаблон для своих элементов управления, в котором вы бы добавили визуальные элементы, которые вы хотите (звездочка и т.д.). Вы можете контролировать их видимость с помощью триггеров. (вы можете задать отдельный вопрос для получения более подробной информации об этом)
Second/Third. Вы можете определить логическое свойство IsRequired
на AgeController
, и вы можете установить его в TRUE/FALSE при определении проверки:
<TextBox.Text>
<Binding Path="blabla" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:AgeController ValidationStep="RawProposedValue"
IsRequired="**True**" />
OR: IsRequired="**False**" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
Затем это значение будет доступно вам при реализации проверки:
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (IsRequired)
{
...
}
else
{
...
}
}
Ответ 4
Вот второй ответ, который не совсем вопрос Массимо.
Я сделал это, думая, что это может быть проще в использовании для дизайнера XAML
Цель состоит в том, чтобы иметь метку (подкласс действительно иметь требуемый красный символ (*)) проще в использовании.
Он дает фокус для TexBlock благодаря обычному целевому свойству
<local:LabelWithRequiredInfo Content="_Firstname"
Target="{Binding ElementName=textboxFirstname, Mode=OneWay}"
... />
И поскольку есть цель, LabelWithRequiredInfo может проверить наличие RequiredValidationRule в TextBox.TextProperty.
Поэтому большую часть времени не нужно для свойства IsRequired.
public LabelWithRequiredInfo()
{
var dpd = DependencyPropertyDescriptor.FromProperty(Label.TargetProperty, typeof(Label));
dpd.AddValueChanged(this, SearchForRequiredValidationRule);
}
private void SearchForRequiredValidationRule(object sender, EventArgs e)
{
var textbox = Target as TextBox;
if (textbox != null)
{
Binding binding = BindingOperations.GetBinding(textbox, TextBox.TextProperty);
var requiredValidationRule = binding.ValidationRules
.OfType<RequiredValidationRule>()
.FirstOrDefault();
if (requiredValidationRule != null)
{
// makes the required legend (red (*) for instance) to appear
IsRequired = true;
}
}
}
И если обязательная легенда должна быть указана в флажке или выпадающем списке или где-либо еще есть свойство IsRequired в LabelWithRequiredInfo
<local:LabelWithRequiredInfo Content="_I agree with the terms of contract"
Target="{Binding ElementName=checkboxIAgree}"
IsRequired='"true"
... />
И еще можно добавить другие правила проверки в текстовое поле (или любой элемент управления), чтобы проверить число, регулярное выражение,...
И последний бонус, установите RequiredLegend как свойство зависимостей в LabelWithRequiredInfo:
public Object RequiredLegend
{
get { return (Object)GetValue(RequiredLegendProperty); }
set { SetValue(RequiredLegendProperty, value); }
}
// Using a DependencyProperty as the backing store for RequiredLegend. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RequiredLegendProperty =
DependencyProperty.Register("RequiredLegend", typeof(Object), typeof(LabelWithRequiredInfo), new PropertyMetadata(null));
Чтобы шаблон LabelWithRequiredInfo мог использовать его для отображения некоторого текста:
<local:LabelWithRequiredInfo RequiredLegend="(*)" ... />
Или что-то большее XAML-ish:
<local:LabelWithRequiredInfo ... >
<local:LabelWithRequiredInfo.RequiredLegend>
<TextBlock Text="(*)" Foreground="Red" />
</local:LabelWithRequiredInfo.RequiredLegend>
Просто нужно изменить шаблон управления в темах \Generic.xaml.
Теперь он использует ContentControl для отображения текста или элемента управления:
<Style TargetType="{x:Type local:LabelWithRequiredInfo}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:LabelWithRequiredInfo}">
<Border ...>
<Grid>
<Grid.ColumnDefinitions ... />
<ContentPresenter ... />
**<ContentControl Content="{TemplateBinding RequiredLegend}" Visibility="{TemplateBinding IsRequired,Converter={StaticResource booleanToVisibilityConverter}}" Grid.Column="1" /> **
</Grid>
Вот ссылка на полное рабочее решение: http://1drv.ms/1MxltVZ
Лучшее кодирование