Я поставил ниже простейший пример, который я смог придумать, чтобы продемонстрировать свою проблему. Я пытаюсь, чтобы включить кнопку на основе 1 из 2 условий 1) Textbox1 видно и содержание являются действительными или 2) TextBox2 видна и содержание являются действительнымиКнопка отключения, основанная на нескольких свойствах. Я использую MultiDataTrigger и MultiBinding
Я, кажется, на моем пути, чтобы позволить их кнопка, основанная на видимости, но аспект IsValid дает мне печаль.
Для кнопки я представил MultiDataTrigger и MultiBinding с помощью метода MultiBinding Converter, чтобы оценить, должна ли кнопка включаться или нет. Метод (называемый myConverter) вызывается, когда я переключаюсь между editboxes (нажатием на переключатель), но, похоже, не вызывается, когда данные в поле редактирования действительны, недействительны или переходят между ними. Вполне возможно, я неправильно обрабатываю Validation.HasError
Мои конкретные вопросы: 1) Каков правильный шаблон для решения этой проблемы? Любые примеры? Я должен сказать, что я упростил проблему. Например, проверка может быть больше, чем просто «должно быть восемь символов», и может быть задействовано несколько блоков ввода (например, «адрес» и «zip» ИЛИ «адрес» и «состояние». Таким образом, я думаю, что мне, вероятно, понадобится Идея MultiBinding Converter, но я открыт для других идей! 2) Как обрабатывать Validation.HasError внутри моего метода конвертера? Я рассматриваю его как ReadOnlyCollection, который, вероятно, полностью ошибается! 3) Я думаю, что большая часть моих проблем связана с множеством вариантов обработки информации об ошибках. Учитывая, что я использую ValidationRules, должен ли я также бросать Исключения из моих свойств, которые возвращают поля редактирования? Будут ли они когда-нибудь вызваны? Можете ли вы порекомендовать статью, показывающую разные способы проверки?
Я поставил ВСЕ код ниже. Я был бы очень признателен, если бы кто-то мог быстро взглянуть и указать мне в правильном направлении. -Dave XAML код
<Window x:Class="StackOverFlowBindingExample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverFlowBindingExample"
Title="Window1" Height="Auto" MinWidth="500" SizeToContent="Manual" WindowStartupLocation="CenterOwner" ResizeMode="CanResizeWithGrip" >
<Window.Resources>
<local:MyConverter x:Key="myConverter" />
<Style x:Key="textStyleTextBox" TargetType="TextBox">
<Setter Property="Foreground" Value="#333333" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="MinHeight" Value="2" />
<Setter Property="MinWidth" Value="100" />
<Setter Property="Margin" Value="4" />
<Setter Property="MaxLength" Value="23" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<RadioButton Name="m_radio1" Margin="4" GroupName="IdInputType" IsChecked="True" Checked="IdInputType_Changed">Use Inputtype1</RadioButton>
<RadioButton Name="m_radio2" Margin="4" GroupName="IdInputType" IsChecked="False" Checked="IdInputType_Changed">Use Inputtype2</RadioButton>
</StackPanel>
<DockPanel Name="Grp8Digit">
<Label MinHeight="25" Margin="4" VerticalAlignment="Top" VerticalContentAlignment="Center" HorizontalAlignment="Left" MinWidth="100" Width="113">Type 1 Id:</Label>
<TextBox Height="23" Name="m_textBox8DigitId" MaxLength="8" Width="120" Style="{StaticResource textStyleTextBox}" Validation.Error="TextBox_Error">
<TextBox.Text>
<Binding>
<Binding.ValidatesOnExceptions>true</Binding.ValidatesOnExceptions>
<Binding.ValidatesOnDataErrors>true</Binding.ValidatesOnDataErrors>
<Binding.UpdateSourceTrigger>PropertyChanged</Binding.UpdateSourceTrigger>
<Binding.Path>EightDigitId</Binding.Path>
<Binding.NotifyOnValidationError>true</Binding.NotifyOnValidationError>
<Binding.ValidationRules>
<local:EightByteStringConvertRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label MinHeight="25" Margin="4" VerticalAlignment="Top" VerticalContentAlignment="Center" HorizontalAlignment="Left" MinWidth="100">Enter 8 digit id</Label>
</DockPanel>
<DockPanel Name="Grp5Digit" Visibility="Collapsed">
<StackPanel Orientation="Horizontal">
<Label MinHeight="25" Margin="4" VerticalAlignment="Top" VerticalContentAlignment="Center" HorizontalAlignment="Left" MinWidth="100" Width="113">Type 2 id:</Label>
<TextBox Name="m_textBox5DigitId" Style="{StaticResource textStyleTextBox}" MinHeight="25" Margin="4" VerticalAlignment="Top" MaxLength="23" VerticalContentAlignment="Center" HorizontalAlignment="Left" MinWidth="100" Width="100" ToolTip="Enter Type 2 id">
<TextBox.Text>
<Binding>
<Binding.ValidatesOnExceptions>true</Binding.ValidatesOnExceptions>
<Binding.Path>FiveDigitId</Binding.Path>
<Binding.NotifyOnValidationError>true</Binding.NotifyOnValidationError>
<Binding.ValidationRules>
<local:FiveByteStringConvertRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label MinHeight="25" Margin="4" VerticalAlignment="Top" VerticalContentAlignment="Center" HorizontalAlignment="Left" MinWidth="100">Enter 5 digit id</Label>
</StackPanel>
</DockPanel>
</StackPanel>
<Button Height="27" Name="btnDoSomething" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="4" HorizontalContentAlignment="Center" Click="btnDoSomething_Click" Content="Do Something">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Value="true">
<Condition.Binding>
<MultiBinding Converter="{StaticResource myConverter}">
<Binding ElementName="Grp8Digit" Path="Visibility" />
<Binding ElementName="m_textBox8DigitId" Path="Validation.HasError" />
<Binding ElementName="Grp5Digit" Path="Visibility" />
<Binding ElementName="m_textBox5DigitId" Path="Validation.HasError" />
</MultiBinding>
</Condition.Binding>
</Condition>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Grid>
C# код
using System;
// lots of usings!!!
namespace StackOverFlowBindingExample
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window, INotifyPropertyChanged
{
private static readonly object eightDigitLock = new object();
private string _eightdigitId;
public string EightDigitId
{
get
{
return _eightdigitId;
}
set
{
lock (eightDigitLock)
{
if (value != _eightdigitId)
{
if (value.Length == 8)
_eightdigitId = value;
else
throw new Exception("Must be 8 digits");// do I really need to throw Exception here?
}
}
}
}
private static readonly object fiveDigitLock = new object();
private string _fivedigitId;
public string FiveDigitId
{
get
{
return _fivedigitId;
}
set
{
lock (fiveDigitLock)
{
if (value != _fivedigitId)
{
if (value.Length == 5)
_fivedigitId = value;
else
throw new Exception("Must be 5 digits");// do I really need to throw exception?
}
}
}
}
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private void IdInputType_Changed(object sender, RoutedEventArgs e)
{
if (m_radio1 != null && Grp8Digit != null && Grp5Digit != null)
{
if (m_radio1.IsChecked == true)
{
Grp8Digit.Visibility = Visibility.Visible;
Grp5Digit.Visibility = Visibility.Collapsed;
}
else
{
Grp8Digit.Visibility = Visibility.Collapsed;
Grp5Digit.Visibility = Visibility.Visible;
}
}
}
private void TextBox_Error(object sender, ValidationErrorEventArgs e)
{
try
{
if (e.Action == ValidationErrorEventAction.Added)
{
try
{
if (e.Error.Exception != null && e.Error.Exception.InnerException != null && e.Error.Exception.InnerException.Message.Length > 0)
{
((Control)sender).ToolTip = e.Error.Exception.InnerException.Message;
}
else
{
((Control)sender).ToolTip = e.Error.ErrorContent.ToString();
}
}
catch (Exception ex)
{
string msg = ex.Message;
//Common.ProgramContext.Current.AddSessionLogEntrySync(new LogEntry(LogEntryCategory.Exception, ex.ToString()));
((Control)sender).ToolTip = e.Error.ErrorContent.ToString();
}
}
else
{
((Control)sender).ToolTip = "";
}
}
catch (Exception)
{
//Common.ProgramContext.Current.AddSessionLogEntrySync(new LogEntry(LogEntryCategory.Exception, ex.ToString()));
((Control)sender).ToolTip = "";
}
}
private void btnDoSomething_Click(object sender, RoutedEventArgs e)
{
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
bool valid8Digit = true;
ReadOnlyCollection<ValidationError> collection = values[1] as ReadOnlyCollection<ValidationError>;
if (collection != null && collection.Count > 0)
{
valid8Digit = false;
}
//if ((bool)values[0] == true)//&& (bool)values[1] == false)
if ((Visibility)values[0] == Visibility.Visible && valid8Digit)
{
return true;
}
else
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class FiveByteStringConvertRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if ((value as string) != null && (value as string).Length == 5)
return new ValidationResult(true, "");
else
return new ValidationResult(false, "");
}
}
public class EightByteStringConvertRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if ((value as string) != null && (value as string).Length == 8)
return new ValidationResult(true, "");
else
return new ValidationResult(false, "");
}
}
}
Я не знаю, если это может относиться к вам, но я нашел эту страницу - http://stackoverflow.com/questions/3120463/databinding-to-validation-haserror. Похоже, может быть проблема, связанная с Validation.HasError, хотя в некоторых других примерах кажется, что это возможно - http://stackoverflow.com/questions/4574304/wpf-validation-how-to-show-tooltips-and -disable-run-button – Dror
@Dave Вы пытаетесь использовать подход ViewModel вместо использования триггеров? – Ankesh
Спасибо за оркестр за ссылки. Вы заметите во второй ссылке, что плакат использует Validation.HasError, но не с концепцией MultiBinding Converter. Думаю, мне это нужно, потому что условие включения кнопки не является тривиальным. Это - «если (A И B) ИЛИ (C И D). MultiDataTrigger.Conditions сами по себе, похоже, не поддаются этому. Он также вводит еще один метод проверки данных, IDataErrorInfo. Не уверен, что это так или нет. Слишком много вариантов! – Dave