基于多个属性禁用按钮。我正在使用 MultiDataTrigger 和 MultiBinding
我在下面放置了我能想到的最简单的例子来演示我的问题。我正在尝试根据 2 个条件之一启用按钮 1) Textbox1 可见并且内容有效 或者 2)Textbox2 可见且内容有效
我似乎正在根据可见性启用按钮,但 IsValid 方面让我感到悲伤。
对于按钮,我引入了 MultiDataTrigger 和 MultiBinding 以及 MultiBinding Converter 方法来评估是否应启用该按钮。当我在编辑框之间切换(通过单击单选按钮)时,会调用该方法(称为 myConverter),但当编辑框中的数据有效、无效或两者之间的转换时,似乎不会调用该方法。很可能,我没有正确处理 Validation.HasError
我的具体问题: 1)处理这个问题的正确模式是什么?有什么例子吗?我应该说我已经简化了问题。例如,验证可能不仅仅是“必须是八个字符”,并且可能涉及多个编辑框(如“地址”和“zip”或“地址”和“状态”。因此我认为我可能需要多重绑定转换器的想法,但我对其他想法持开放态度! 2)如何在 Converter 方法中处理 Validation.HasError?我将其视为 ReadOnlyCollection 这可能是完全错误的! 3)我认为我的问题很大一部分是由于处理错误信息的多种选择造成的。鉴于我正在使用 ValidationRules,我是否也应该从支持编辑字段的属性中抛出异常?他们会被召唤吗?您能推荐一篇展示不同验证方法的文章吗?
我已将所有代码放在下面。如果有人能快速浏览一下并为我指出正确的方向,我将不胜感激。 -戴夫 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, "");
}
}
}
I have placed below the simplest example I could come up with to demonstrate my problem. I am trying to to enable a button based on 1 of 2 conditions
1) Textbox1 is visible AND contents are valid
or
2) Textbox2 is visible AND contents are valid
I seem to be on my way to enabling the button based on visibility, but the IsValid aspect is giving me grief.
For the button, I have introduced a MultiDataTrigger and MultiBinding with a MultiBinding Converter method to evaluate whether the button should be enabled or not. The method (called myConverter) is called when I switch between editboxes (by clicking a radio button), but does not seem to be called when the data in the edit box is valid, invalid, or transitions between the two. Quite possibly, I'm not correctly handling Validation.HasError
My specific questions:
1) What's the correct pattern to handle this problem? Any examples? I should say that I've simplified the problem. For example, the validation might be more than just "must be eight characters", and there could be multiple edit boxes involved (like "address" and "zip" OR "address" and "state". Thus I think I probably need the MultiBinding Converter idea but I'm open to other ideas!
2) How do I handle Validation.HasError inside my Converter method? I'm treating it as ReadOnlyCollection which is probably totally wrong!
3) I think a large part of my problems are due to the many choices to handle error info. Given that I'm using ValidationRules, should I also be throwing Exceptions from my properties that back the edit fields? Will they ever be called? Can you recommend an article showing the different ways to do validation?
I've put ALL the code below. I'd be most appreciative if someone could take a quick look and point me in the right direction.
-Dave
XAML code
<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# code
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, "");
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您应该使用命令来禁用/启用按钮。这是做您想做的事情的最简单、最干净的方式。
在代码隐藏文件中,声明一个新的静态类 Commands,并声明一个新的 RoutedUICommand。
要使用它,您需要在 Window/UserControl 中声明 CommandBinding。
my: 是我的本地命名空间。
然后您只需设置按钮即可使用该命令。
CommandBinding 的 CanExecute 和 Executed 事件是您的逻辑所在的位置。
要禁用/启用该按钮,只需在 DoSomethingCanExecute 中进行处理即可。
当然,执行事件是当用户单击按钮时发生的事件。
编辑
验证事件仅在绑定更新时触发。要强制进行验证,您可以在窗口/用户控件加载后立即手动更新触发器。在窗口的 Loaded 事件中:
You should use commands to disable/enable buttons. It's the easiest and cleanest way of doing what you wish to do.
In your code-behind file, declare a new static class, Commands, and declare a new RoutedUICommand.
To use this, you need to declare a CommandBinding in your Window/UserControl.
my: is my local namespace.
Then you can simply set the button to use that command.
The CommandBinding's CanExecute and Executed events are where your logic should lie.
To disable/enable the button, simply handle that in DoSomethingCanExecute.
And of course, the Executed event is what happens when the user clicks the button.
EDIT
The validation event only triggers when bindings update. To force a validation, you could update the triggers by hand as soon as the window/usercontrol has loaded. In the Window's Loaded event: