Stop ValidationRule if ComboBox is Collapsed - wpf

I have two ComboBox's - cbo_client_pay_method & cbo_terms
One of the cbo_client_pay_method items (On Account) requires cbo_terms (30 days etc...) to be Visible else its Collapsed, I have this functionality already set up in cbo_payment_type_SelectionChanged event.
I have implemented a validationRule that test if the cbo's are not null && if selectedValue <0 (something is selected), this works ok.
This all works great unless the cbo's are collapsed, the validation still fires!
Can I halt the validationRule if the element is collapsed?
<StackPanel Name="sp_account" Orientation="Horizontal" VerticalAlignment="Center">
<Label Content="Payment" Style="{StaticResource formLabel}"/>
<Grid>
<ComboBox Name="cbo_client_pay_method" Style="{StaticResource reminder_cbo}" SelectionChanged="cbo_client_payMethod_SelectionChanged" Validation.ErrorTemplate="{StaticResource validationTemplate}">
<ComboBox.SelectedValue>
<Binding Path="client_payment_type_id" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:ValidCbo ErrorMessage="Select A Payment Type" />
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedValue>
</ComboBox>
<TextBlock Name="txtSelectPayMethod" Text="Please Select A Payment Method..." Style="{StaticResource cbo_overlay}" />
</Grid>
</StackPanel>
<StackPanel Name="sp_terms" Orientation="Horizontal" VerticalAlignment="Center">
<Label Content="Terms" Style="{StaticResource formLabel}"/>
<Grid>
<ComboBox Name="cbo_terms" Style="{StaticResource reminder_cbo}" SelectionChanged="cbo_terms_SelectionChanged" Validation.ErrorTemplate="{StaticResource validationTemplate}">
<ComboBox.SelectedValue>
<Binding Path="terms_id" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:ValidCbo ErrorMessage="Select Payment Terms" />
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedValue>
</ComboBox>
<TextBlock Name="txtSelectTerms" Text="Please Select Payment Terms..." Style="{StaticResource cbo_overlay}" />
</Grid>
</StackPanel>
public class ValidCbo : ValidationRule
{
private string _errorMessage;
public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
//if (this.ErrorMessage.Contains("Master") |)
if (value == null )
{
// value = null
return new ValidationResult(false, this.ErrorMessage);
}
else
{
// Not null
int selectedValue = (int)value;
if (selectedValue < 0)
{
return new ValidationResult(false, this.ErrorMessage);
}
else
{
return ValidationResult.ValidResult;
}
}
}
}

You could apply a style which only binds the value if the ComboBox is visible:
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Setter Property="SelectedValue">
<Setter.Value>
<Binding Path="terms_id" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<local:ValidCbo ErrorMessage="Select Payment Terms" />
</Binding.ValidationRules>
</Binding>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
You must not set the SelectedValue in the ComboBox itself or it will overwrite the style though.

Related

XamlParseException datagrid

I get a XamlParseException and I don't know how to solve it. I want to be able to control the input.
xaml
<DataGridTemplateColumn Header="Naam" CellTemplate="{StaticResource ContactNameTemplate}" CellEditingTemplate="{StaticResource EditingContactNameTemplate}"/>
dictionary
<DataTemplate x:Key="ContactNameTemplate" >
<TextBox HorizontalAlignment="Center" Validation.ErrorTemplate="{StaticResource validationTemplate}" Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding Path="Name" Source="{Binding Name}" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<c:Text Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
<DataTemplate x:Key="EditingContactNameTemplate">
<TextBox HorizontalAlignment="Center" Validation.ErrorTemplate="{StaticResource validationTemplate}" Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding Path="Name" Source="{Binding Name}" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<c:Text Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DataTemplate>
edit
my error
A first chance exception of type 'System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll
Additional information: A Binding can not be set in the Source property of type Binding. A binding can only be set in a DependencyProperty of a DependencyObject.
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self},Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
public class Text : ValidationRule
{
private int _min;
private int _max;
public Text()
{
}
public int Min
{
get { return _min; }
set { _min = value; }
}
public int Max
{
get { return _max; }
set { _max = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
ValidationResult result;
string text;
if (((string)value).Length < Min || ((string)value).Length > Max)
{
text = value.ToString();
result = new ValidationResult(true, null);
}
else
{
text = "";
result = new ValidationResult(false, "De naam moet tss " + Min + " en " + Max);
}
return result;
}
}

Validate two TextBox.Text values to be distinct

Grab two TextBox and say that you need to validate that their content are distinct strings.
Example :
Correct result : prefix1, prefix2
Incorrect result : prefix1, prefix1
To do that task I thought about using a MultiBinding but two problems arises then :
Where should it be placed ? currently it is on a dummy TextBox
Even using that dummy TextBox, the ValidationRule is never called
Not sure whether this approach is correct, how would you do that ?
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding Path="GradientPrefix"
Source="{StaticResource Indices}"
UpdateSourceTrigger="PropertyChanged" />
<Binding Path="ColorPrefix"
Source="{StaticResource Indices}"
UpdateSourceTrigger="PropertyChanged" />
<MultiBinding.ValidationRules>
<gpl2Xaml:DistinctStringValidationRule />
</MultiBinding.ValidationRules>
</MultiBinding>
Here's the solution using a BindingGroup !
Error at BindingGroup level :
Error at BindingGroup and field levels :
No errors :
Here's the code :
<Window>
<Window.Resources>
<gpl2Xaml:Indices x:Key="Indices"
ColorIndex="1"
ColorPrefix="MyColor"
GradientIndex="1"
GradientPrefix="MyColor" />
</Window.Resources>
<Grid DataContext="{StaticResource Indices}"
Style="{StaticResource gridInError}"
Validation.ErrorTemplate="{StaticResource validationTemplate}">
<Grid.BindingGroup>
<BindingGroup>
<BindingGroup.ValidationRules>
<gpl2Xaml:DistinctValidationRule />
</BindingGroup.ValidationRules>
</BindingGroup>
</Grid.BindingGroup>
<TextBox x:Name="TextBoxGradientPrefix"
Style="{StaticResource textBoxInError}"
TextChanged="TextBoxGradientPrefix_OnTextChanged"
Validation.ErrorTemplate="{StaticResource validationTemplate}">
<Binding Path="GradientPrefix"
Source="{StaticResource Indices}"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<gpl2Xaml:StringValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
<TextBox x:Name="TextBoxColorPrefix"
Style="{StaticResource textBoxInError}"
TextChanged="TextBoxColorPrefix_OnTextChanged"
Validation.ErrorTemplate="{StaticResource validationTemplate}">
<Binding Path="ColorPrefix"
Source="{StaticResource Indices}"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<gpl2Xaml:StringValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
</Grid>
</Window>
Extra code to trigger validation every time :
private void TextBoxGradientPrefix_OnTextChanged(object sender, TextChangedEventArgs e)
{
grid.BindingGroup.CommitEdit();
}
private void TextBoxColorPrefix_OnTextChanged(object sender, TextChangedEventArgs e)
{
grid.BindingGroup.CommitEdit();
}
And the validation rule :
public class DistinctValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var bindingGroup = value as BindingGroup;
if (bindingGroup == null) return new ValidationResult(false, "Not a BindingGroup");
var o = bindingGroup.Items[0] as Indices;
if (o == null) return new ValidationResult(false, "Not an Indices");
if (o.ColorPrefix == o.GradientPrefix)
return new ValidationResult(false, "Color prefix and Gradient prefix must be distinct.");
return new ValidationResult(true, null);
}
}

WPF Bind many to one relationship returns empty results

I have a Booking which has a list of TourGuides and I'm trying to create a Booking and list of TourGuides at the same time, the Booking binding is working, but the TourGuideOnTour is always coming back as null.
heres my models:
public class Booking : INotifyPropertyChanged {
public IList<TourGuide> TourGuidesOnTour {
get {
if (_tourGuidesOnTour == null)
return new List<TourGuide>();
return _tourGuidesOnTour;
}
set {
_tourGuidesOnTour = value;
OnPropertyChanged("TourGuidesOnTour");
}
}
}
public class TourGuide : INotifyPropertyChanged {
string _tourGuideFirstName;
public string TourGuideFirstName {
get { return _tourGuideFirstName; }
set {
_tourGuideFirstName = value;
OnPropertyChanged("TourGuideFirstName");
}
}
}
And here are my bindings:
<Grid Name="myGrid" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel Margin="10">
<StackPanel.Resources>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Margin" Value="0,10,0,0"/>
</Style>
</StackPanel.Resources>
<GroupBox Header="Tour Information">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Tour" Width="130"/>
<TextBox Grid.Row="1" Grid.Column="1" Width="185">
<TextBox.Text>
<Binding Path="TourName" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</GroupBox>
<GroupBox Header="Tour Guide Information">
<DataGrid ItemsSource="{Binding Path=TourGuidesOnTour, Mode=TwoWay}">
</DataGrid>
</GroupBox>
<Button Name="Save" Content="Save" Click="Save_Click" Width="170" Height="40" />
</StackPanel>
</Grid>
I set my Datacontext:
public Booking ReturnValue;// = null;
public CreateBooking() {
InitializeComponent();
ReturnValue = new Booking();
myGrid.DataContext = ReturnValue;
}
and ReturnValue.TourGuidesOnTour is equal to null :(
Can anyone tell me why?
I don't see anywhere in your posted code where you are initialising the TourGuidesOnTour, i.e;
ReturnValue.TourGuidesOnTour = new List<TourGuide>();
If you don't, your getter will always return a new list of TourGuide, but never initialise the internal variable.
So, you could either Initialise the TourGuidesOnTour or perhaps if you modify your getter to the following;
get
{
if (_tourGuidesOnTour == null)
_tourGuidesOnTour= new List<TourGuide>();
return _tourGuidesOnTour;
}

Databinding one property on a Dialog from two contols

I have a Window that I'm showing using ShowDialog. One of the values I'm trying to get from the user is size in GB or TB. So I have two controls for this, an IntegerUpDown from the WPF Extended Toolkit and a ComboBox:
<xctk:IntegerUpDown Name="SizeN" Minimum="1" Maximum="1023" Increment="1" Value="100"/>
<ComboBox Name="SizeS" SelectedIndex="0">
<ComboBoxItem>GB</ComboBoxItem>
<ComboBoxItem>TB</ComboBoxItem>
</ComboBox>
I am setting the Dialog's DataContext to itself. I have defined the Capacity property:
public ulong Capacity { get; set; }
public CustomWindow()
{
InitializeComponent();
DataContext = this;
}
I have already created an IMultiValueConverter, PowerConverter, that takes an int and a string and returns ulong. I think the correct MultiBinding is:
<Window.Resources>
<local:PowerConverter x:Key="CapacityConverter" />
</Window.Resources>
<MultiBinding Converter="{StaticResource CapacityConverter}">
<Binding ElementName="SizeN" Path="Value" />
<Binding ElementName="SizeS" Path="SelectedValue" />
</MultiBinding>
I can't figure out how to assign this binding to the Capacity property on the Dialog. I want WPF to automagically set the Capacity property for me. Any ideas?
I had to convert Capacity into a DependencyProperty on CustomWindow, set the SelectedValuePath attribute on the ComboBox, and assign the binding to Capacity in the style.
XAML:
<Window xmlns:="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xctk="clr-namespace:Xceed.Wpf.Toolkit;assembly=WPFToolkit.Extended"
xmlns:local="clr-namespace:MyProject"
x:Class="MyProject.CustomWindow" Title="CustomWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<local:PowerConverter x:Key="CapacityConverter" />
</Window.Resources>
<Window.Style>
<Style TargetType="{x:Type local:CustomWindow}">
<Setter Property="Capacity">
<Setter.Value>
<MultiBinding Converter="{StaticResource CapacityConverter}"
Mode="TwoWay">
<Binding ElementName="SizeNumber" Path="Value"
Mode="TwoWay" />
<Binding ElementName="SizeSuffix" Path="SelectedValue"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Window.Style>
<StackPanel>
<xctk:IntegerUpDown Name="SizeNumber" Minimum="1" Maximum="1023" Increment="1"
Value="100"/>
<ComboBox Name="SizeSuffix" SelectedIndex="0" SelectedValuePath="Content">
<ComboBoxItem>GB</ComboBoxItem>
<ComboBoxItem>TB</ComboBoxItem>
</ComboBox>
</StackPanel>
</Window>
Code behind:
public partial class CustomWindow : Window
{
public CustomWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty CapacityProperty =
DependencyProperty.Register("Capacity", typeof(ulong), typeof(CustomWindow));
public ulong Capacity
{
get
{
return (ulong)GetValue(CapacityProperty);
}
set
{
SetValue(CapacityProperty, value);
}
}
}

Can I do Text search with multibinding

I have below combo box in mvvm-wpf application. I need to implement "Text search" in this..(along with multibinding). Can anybody help me please.
<StackPanel Orientation="Horizontal">
<TextBlock Text="Bid Service Cat ID"
Margin="2"></TextBlock>
<ComboBox Width="200"
Height="20"
SelectedValuePath="BidServiceCategoryId"
SelectedValue="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=DataContext.SelectedBidServiceCategoryId.Value}"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}},
Path=DataContext.BenefitCategoryList}"
Margin="12,0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId" />
<Binding Path="BidServiceCategoryName" />
</MultiBinding>
</TextBlock.Text></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
Unfortunately, TextSearch.Text doesn't work in a DataTemplate. Otherwise you could have done something like this
<ComboBox ...>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="TextSearch.Text">
<Setter.Value>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId"/>
<Binding Path="BidServiceCategoryName"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
However this won't work, so I see two solutions to your problem.
First way
You set IsTextSearchEnabled to True for the ComboBox, override ToString in your source class and change the MultiBinding in the TextBlock to a Binding
Xaml
<ComboBox ...
IsTextSearchEnabled="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
Source class
public class TheNameOfYourSourceClass
{
public override string ToString()
{
return String.Format("{0}: {1}", BidServiceCategoryId, BidServiceCategoryName);
}
//...
}
Second Way
If you don't want to override ToString I think you'll have to introduce a new Property in your source class where you combine BidServiceCategoryId and BidServiceCategoryName for the TextSearch.TextPath. In this example I call it BidServiceCategory. For this to work, you'll have to call OnPropertyChanged("BidServiceCategory"); when BidServiceCategoryId or BidServiceCategoryName changes as well. If they are normal CLR properties, you can do this in set, and if they are dependency properties you'll have to use the property changed callback
Xaml
<ComboBox ...
TextSearch.TextPath="BidServiceCategory"
IsTextSearchEnabled="True">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}: {1}">
<Binding Path="BidServiceCategoryId" />
<Binding Path="BidServiceCategoryName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
Source class
public class TheNameOfYourSourceClass
{
public string BidServiceCategory
{
get
{
return String.Format("{0}: {1}", BidServiceCategoryId, BidServiceCategoryName);
}
}
private string m_bidServiceCategoryId;
public string BidServiceCategoryId
{
get
{
return m_bidServiceCategoryId;
}
set
{
m_bidServiceCategoryId = value;
OnPropertyChanged("BidServiceCategoryId");
OnPropertyChanged("BidServiceCategory");
}
}
private string m_bidServiceCategoryName;
public string BidServiceCategoryName
{
get
{
return m_bidServiceCategoryName;
}
set
{
m_bidServiceCategoryName = value;
OnPropertyChanged("BidServiceCategoryName");
OnPropertyChanged("BidServiceCategory");
}
}
}
I don't know if your text search has to search ALL the text, but if you want to search from the category ID, you can just set the TextSearch.TextPath property to BidServiceCategoryId. That should also be helpful for anyone who wants to use multibinding and finds that the text search no longer works... It does work if you explicitly set the TextPath property.

Resources