Add a warning icon to the TextBox when validation - wpf

I want to validate a textbox if something is wrong. The idea is if something is wrong than the next TextBox should have a warning image.
<TextBox Text="{Binding Age, UpdateSourceTrigger=PropertyChanged}">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<!-- Placeholder for the TextBox itself -->
<AdornedElementPlaceholder x:Name="textBox"/>
<image source="some-Image.png" width="20" Height="20" />
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
But the thing is the image is not showing, it only shows the border of the icon.
Am I using AdornedElementPlaceholder correctly?

Tested solution that works and displays the image when error occurs:
<TextBox BorderThickness="0.8">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder/>
<Image Source="Image.jpg" Width="20" Height="20"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
<TextBox.Text>
<Binding Path="ValidationTest" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<validation:IntegerValidationRule ValidationStep="CommittedValue" Min="1" Max="99999999"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
And here is the validation rule that I have used for this example:
public class IntegerValidationRule : ValidationRule
{
private int _min = int.MinValue;
private int _max = int.MaxValue;
private string _fieldName = "Field";
private string _customMessage = String.Empty;
public int Min
{
get { return _min; }
set { _min = value; }
}
public int Max
{
get { return _max; }
set { _max = value; }
}
public string FieldName
{
get { return _fieldName; }
set { _fieldName = value; }
}
public string CustomMessage
{
get { return _customMessage; }
set { _customMessage = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
int num = 0;
var val = (value as BindingExpression).DataItem;
if (!int.TryParse(value.ToString(), out num))
return new ValidationResult(false, $"{FieldName} must contain an integer value.");
if (num < Min || num > Max)
{
if (!String.IsNullOrEmpty(CustomMessage))
return new ValidationResult(false, CustomMessage);
return new ValidationResult(false, $"{FieldName} must be between {Min} and {Max}.");
}
return new ValidationResult(true, null);
}
}
Which is just modified example from MSDN Docs
Some notes:
You didn't provide the validation rule so I am assuming it works as expected and produces valid validation result.
The Binding of your TextBox.Text property doesn't include the Validation rule.

Related

Cancel TextBox input on validation errors

The standard behavior in TextBoxes when a input string is not valid is to show a red square (for example user introduces a letter in numeric TextBox). This happens when TextBox loses focus.
I want implement this behavior:
The textBox loses focus.
TextBox do internal validation (date, numeric, etc).
If input user string is not valid, old value is restored and TextBox don't show any error.
you have an exemple of validation in textbox here in this link: http://www.codeproject.com/Tips/690130/Simple-Validation-in-WPF
<ControlTemplate x:Key="validationErrorTemplate">
<DockPanel>
<TextBlock Foreground="Red"
DockPanel.Dock="Top">!</TextBlock>
<AdornedElementPlaceholder
x:Name="ErrorAdorner"
></AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
public class NameValidator : ValidationRule
{
public override ValidationResult Validate
(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value == null)
return new ValidationResult(false, "value cannot be empty.");
else
{
if (value.ToString().Length > 3)
return new ValidationResult
(false, "Name cannot be more than 3 characters long.");
}
return ValidationResult.ValidResult;
}
}
<TextBox Height="23" HorizontalAlignment="Left"
Grid.Column="1" Grid.Row="0" Name="textBox1"
VerticalAlignment="Top" Width="120"
Validation.ErrorTemplate="{StaticResource validationErrorTemplate}"
>
<TextBox.Text>
<Binding Path="Name" Mode="TwoWay"
UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<local:NameValidator></local:NameValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Reverting to the old value is quite easy if you use MVVM. In your ViewModel's prop setter you can simply not set the new value to the model if it's invalid and invoke PropertyChanged instead. This will tell the bound view element to call your property getter, which will return the old value and thus revert the view element's content to the old value.
Example (validating that user input is an int value):
public string Number
{
get { return _model.Number.ToString(); }
set
{
if (_model.Number.ToString() != value)
{
int number;
if (int.TryParse(value, out number))
{
_model.Number = number;
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Number));
}
}
}

Trouble Parameterizing ValidationRules with Dependency Properties

so I have written the following DP and ValidationRule:
public class ComparisonValue : DependencyObject
{
public Object ComparisonObject
{
get { return (Object)GetValue(ComparisonObjectProp); }
set {
SetValue(ComparisonObjectProp, value);
}
}
public static readonly DependencyProperty ComparisonObjectProp =
DependencyProperty.Register("ComparisonObject", typeof(object), typeof(ComparisonValue), new UIPropertyMetadata(null));
}
public class ObjectComparisonValidator : ValidationRule
{
private ComparisonValue _ObjectToCompare;
public ComparisonValue ObjectToCompare
{
get
{
return _ObjectToCompare;
}
set
{
_ObjectToCompare = value;
}
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value != null)
{
if (!value.Equals(ObjectToCompare.ComparisonObject))
{
return new ValidationResult(false, "Values are not equal");
}
else
{
return new ValidationResult(true, null);
}
}
else
{
if (value != ObjectToCompare.ComparisonObject)
{
return new ValidationResult(false, "Values are not equal");
}
else
{
return new ValidationResult(true, null);
}
}
}
}
Then in my XAML I have the following markup:
<UserControl.Resources>
<l:EnumToStringConverter x:Key="CustomEnumConverter"/>
<l:BooleanToBrushConverter x:Key="BooleanToBrushConverter"/>
<l:ObjectComparisonValidator x:Key="ObjectComparisonValidator"/>
<l:ComparisonValue x:Key="ComparisonValue"/>
</UserControl.Resources>
....
<TextBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Height="25" Text="{Binding Path=NetworkKey.Value, UpdateSourceTrigger=PropertyChanged}">
<TextBox.Background>
<Binding Path="NetworkKey.Changed" Converter="{StaticResource BooleanToBrushConverter}">
<Binding.ConverterParameter>
<x:Array Type="Brush">
<SolidColorBrush Color="Yellow"/>
<SolidColorBrush Color="White"/>
</x:Array>
</Binding.ConverterParameter>
</Binding>
</TextBox.Background>
</TextBox>
....
<TextBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Height="25">
<TextBox.Text>
<Binding Path="DuplicateNetworkKey.Value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<l:ObjectComparisonValidator>
<l:ObjectComparisonValidator.ObjectToCompare>
<l:ComparisonValue ComparisonObject="{Binding Path=NetworkKey.Value}"/>
</l:ObjectComparisonValidator.ObjectToCompare>
</l:ObjectComparisonValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<TextBox.Background>
<Binding Path="DuplicateNetworkKey.Changed" Converter="{StaticResource BooleanToBrushConverter}">
<Binding.ConverterParameter>
<x:Array Type="Brush">
<SolidColorBrush Color="Yellow"/>
<SolidColorBrush Color="White"/>
</x:Array>
</Binding.ConverterParameter>
</Binding>
</TextBox.Background>
</TextBox>
Now the problem I am having is that the Validate method fo the Validation rule gets invoked, but when the binding for NetworkKey gets triggered, the Setter in the ComparisonValue for the object never gets invoked, so any time the validation rule runs, the ComparisonObject property of ObjectComparisonValidator.ObjectToCompare is null, and thus validation fails. Whats wrong with the binding I have for ComparisonObject?
just for a bit of clarification, the type of NetworkKey and DuplicateKey (props in the VM) are INPC classes. Heres the code for them as well:
public class ValueField<T> : AChangeReportingViewModel, INotifyPropertyChanged
{
private T _OriginalVal;
public T OriginalVal
{
get
{
return _OriginalVal;
}
set
{
_OriginalVal = value;
Value = value;
Changed = false;
PropertyChanged(this, new PropertyChangedEventArgs("OriginalVal"));
}
}
private T _Value;
public T Value
{
get
{
return _Value;
}
set
{
_Value = value;
if (_Value == null)
{
if (_OriginalVal != null) Changed = true;
}
else
{
Changed = !_Value.Equals(_OriginalVal);
}
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
private Boolean _Changed;
public Boolean Changed
{
get
{
return _Changed;
}
set
{
if (_Changed != value)
{
if (value) ChangeMade();
else ChangeReversed();
}
_Changed = value;
PropertyChanged(this, new PropertyChangedEventArgs("Changed"));
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
<Binding Path="DuplicateNetworkKey.Value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<l:ObjectComparisonValidator>
<l:ObjectComparisonValidator.ObjectToCompare>
<l:ComparisonValue ComparisonObject="{Binding Path=NetworkKey.Value}"/>
</l:ObjectComparisonValidator.ObjectToCompare>
</l:ObjectComparisonValidator>
</Binding.ValidationRules>
</Binding>
The inner binding object is not part of the visual tree, and so does not inherit the data context of the parent. To bind outside of the visual tree, use an x:Reference binding:
<l:ComparisonValue ComparisonObject="{Binding Source={x:Reference Root}
Path=DataContext.NetworkKey.Value}"/>
It's similar to an ElementName binding, but you can't do those outside of the visual tree. Note that "Root" in this example is the name of the root UI element.

How to implement binding validation for a textbox?

How to implement binding validation for a textbox?
<TextBox Name="textBox1" Width="50" FontSize="15"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}"
Grid.Row="1" Grid.Column="1" Margin="2">
<TextBox.Text>
<Binding Path="Age" Source="{StaticResource ods}"
UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
// *** What should I write here? ***
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
What should I write in the validation Rule?
The following example shows the implementation of AgeRangeRule, which inherits from ValidationRule and overrides the Validate method. The Int32.Parse() method is called on the value to make sure that it does not contain any invalid characters. The Validate method returns a ValidationResult that indicates if the value is valid based on whether an exception is caught during the parsing and whether the age value is outside of the lower and upper bounds.
public class AgeRangeRule : ValidationRule
{
private int _min;
private int _max;
public AgeRangeRule()
{
}
public int Min
{
get { return _min; }
set { _min = value; }
}
public int Max
{
get { return _max; }
set { _max = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
int age = 0;
try
{
if (((string)value).Length > 0)
age = Int32.Parse((String)value);
}
catch (Exception e)
{
return new ValidationResult(false, "Illegal characters or " + e.Message);
}
if ((age < Min) || (age > Max))
{
return new ValidationResult(false,
"Please enter an age in the range: " + Min + " - " + Max + ".");
}
else
{
return new ValidationResult(true, null);
}
}
}
To expand on Cornel's answer, here is the related XAML that you would use with his example code:
<TextBox Name="textBox1" Width="50" FontSize="15"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}"
Grid.Row="1" Grid.Column="1" Margin="2"> <TextBox.Text>
<Binding Path="Age" Source="{StaticResource ods}"
UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<c:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding></TextBox.Text></TextBox>
So in summary, you create your custom Validation class, reference it in your XAML code, and then implement it with the necessary properties initialized.
MSDN example

Binding ValidationRule from TextBox to StatusBarItem and displaying the error message

I am creating an application where I want to use validation rules, but on some screens there is not enough space to display the resulting error alongside the field which is in error, so I want to put it in a status bar at the bottom of the field.
This sample is from several bits I have pieced together from the web which gives a form which validates using different rules and displays the errors in different ways, but I cant see how to get the error message into the StatusBarItem using XAML. I feel sure there is a simple way to do it. Can anyone help me please?
The sample was written in VS2010 using Framework 4.0.
MainWindow.xaml
<Window x:Class="SampleValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SWL.Libraries.SysText"
Title="Sample ValidationRule WPF" Height="350" Width="525"
Loaded="Window_Loaded" WindowStartupLocation="CenterScreen">
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<!--
<TextBlock Foreground="Red" FontSize="20">***</TextBlock>
-->
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Foreground="Red" Margin="5" FontSize="8pt" Text="***" />
<AdornedElementPlaceholder />
</DockPanel>
</ControlTemplate>
<Style x:Key="textBoxInError1" 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>
<Style x:Key="textBoxInError2" TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Foreground="Red" Margin="5" FontSize="8pt"
Text="{Binding ElementName=MyAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" />
<Border BorderBrush="Red" BorderThickness="2">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<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>
<TextBlock Height="24" HorizontalAlignment="Left" Margin="36,73,0,0" Name="textBlock1"
Text="Node Address:" VerticalAlignment="Top" Width="87" />
<TextBlock Height="24" HorizontalAlignment="Left" Margin="36,112,0,0" Name="textBlock2"
Text="Node Name:" VerticalAlignment="Top" Width="78" />
<TextBox Height="24" HorizontalAlignment="Left" Margin="129,70,0,0" Name="textBox1"
VerticalAlignment="Top" Width="119" TextChanged="textBox1_TextChanged"
TabIndex="0" Style="{StaticResource textBoxInError2}"
Validation.ErrorTemplate="{StaticResource validationTemplate}">
<Binding Path="NodeAddress" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<c:NumberRangeRule Min="1" Max="100" />
</Binding.ValidationRules>
</Binding>
</TextBox>
<TextBox Height="24" HorizontalAlignment="Left" Margin="129,109,0,0" Name="textBox2"
VerticalAlignment="Top" Width="119" TextChanged="textBox2_TextChanged"
TabIndex="1" Style="{StaticResource textBoxInError2}">
<Binding Path="NodeName" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<c:NameFormatRule MinLength="6" MaxLength="9" />
</Binding.ValidationRules>
</Binding>
</TextBox>
<StatusBar Height="23" HorizontalAlignment="Stretch" Margin="0,0,0,0"
Name="myStatusBar" VerticalAlignment="Bottom">
<StatusBarItem x:Name="errorStatusBarItem" Content="No errors" />
</StatusBar>
<Button Content="Close" Height="29" HorizontalAlignment="Left" Margin="108,227,0,0"
Name="btnCLOSE" VerticalAlignment="Top" Width="85" Click="btnCLOSE_Click" TabIndex="3" />
<Button Content="Apply" Height="29" HorizontalAlignment="Left" Margin="297,227,0,0"
Name="btnAPPLY" VerticalAlignment="Top" Width="85" Click="btnAPPLY_Click" TabIndex="2" />
</Grid>
</Window>
MainWindow.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SampleValidation {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public int NodeAddress { get; set; }
public string NodeName { get; set; }
public bool IsAllLoaded { get; set; }
public MainWindow() {
NodeAddress = 1;
NodeName = "freddy";
IsAllLoaded = false;
InitializeComponent();
btnAPPLY.Visibility = System.Windows.Visibility.Hidden;
DataContext = this;
}
private void btnAPPLY_Click(object sender, RoutedEventArgs e) {
// if there are no format errors reported by the validation rules
Validator.ErrorText = "";
if (Validator.IsValid(this))
// Save the data
btnAPPLY.Visibility = System.Windows.Visibility.Hidden; // hide the button indicating nothing new to save
else
MessageBox.Show("Cant Save Changes - Error in form\r\n" + Validator.ErrorText, "Save not allowed", MessageBoxButton.OK, MessageBoxImage.Error);
}
private void btnCLOSE_Click(object sender, RoutedEventArgs e) {
if (btnAPPLY.Visibility != System.Windows.Visibility.Hidden) {
MessageBoxResult myAnswer = MessageBox.Show("Save Changes?", "Confirmation", MessageBoxButton.YesNoCancel);
if (myAnswer == MessageBoxResult.Cancel)
return;
if (myAnswer == MessageBoxResult.Yes)
btnAPPLY_Click(sender, e);
}
this.Close();
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
IsAllLoaded = true;
}
private void ShowModified() {
if (IsAllLoaded)
btnAPPLY.Visibility = System.Windows.Visibility.Visible;
} // ShowModified
private void textBox2_TextChanged(object sender, TextChangedEventArgs e) {
ShowModified();
}
private void textBox1_TextChanged(object sender, TextChangedEventArgs e) {
ShowModified();
}
} // class MainWindow
public static class Validator {
public static string ErrorText { get; set; }
static Validator() {
ErrorText = "";
}
public static bool IsValid(DependencyObject parent) {
// Validate all the bindings on the parent
bool valid = true;
LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
while (localValues.MoveNext()) {
LocalValueEntry entry = localValues.Current;
if (BindingOperations.IsDataBound(parent, entry.Property)) {
Binding binding = BindingOperations.GetBinding(parent, entry.Property);
if (binding.ValidationRules.Count > 0) {
BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
expression.UpdateSource();
if (expression.HasError) {
ErrorText = expression.ValidationError.ErrorContent.ToString();
valid = false;
}
}
}
}
// Validate all the bindings on the children
System.Collections.IEnumerable children = LogicalTreeHelper.GetChildren(parent);
foreach (object obj in children) {
if (obj is DependencyObject) {
DependencyObject child = (DependencyObject)obj;
if (!IsValid(child)) {
valid = false;
}
}
}
return valid;
}
} // class Validator
ValidationRules.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Windows.Controls;
namespace SWL.Libraries.SysText {
public class NumberRangeRule : ValidationRule {
private int _min;
private int _max;
public NumberRangeRule() { }
public int Min {
get { return _min; }
set { _min = value; }
}
public int Max {
get { return _max; }
set { _max = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
int val = 0;
try {
if (((string)value).Length > 0)
val = Int32.Parse((String)value);
} catch (Exception e) {
return new ValidationResult(false, "Illegal Characters or " + e.Message);
}
if ((val < Min) || (val > Max)) {
return new ValidationResult(false, "Please Enter Number in Range: " + Min + " - " + Max + ".");
} else {
return new ValidationResult(true, null);
}
}
}
public class NameFormatRule : ValidationRule {
private int _minLength;
private int _maxLength;
public NameFormatRule() { }
public int MinLength {
get { return _minLength; }
set { _minLength = value; }
}
public int MaxLength
get { return _maxLength; }
set { _maxLength = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo) {
try {
if (((string)value).Length > 0) {
if (((string)value).Length < MinLength || ((string)value).Length > MaxLength)
return new ValidationResult(false, String.Format ("Enter a string of {0} to {1} characters in length", MinLength, MaxLength));
return new ValidationResult(true, null);
}
return new ValidationResult(true, null);
} catch (Exception e) {
return new ValidationResult(false, "Illegal Characters or " + e.Message);
}
}
}
}
UPDATE
I recently had to handle multiple error messages that were displayed when the mouse hovered an error icon. I made my viewmodel implement IDataErrorInfo. I then created a class-level Dictionary, keyed by a unique identifier for each control (I just used an enum), in my viewmodel to hold one error per control. Then I created a public property of type string and called it ErrorText. The getter of ErrorText iterates the errors dictionary and builds out all the errors into one string.
Here's an example, it may need tweaked. I know this is extremely simplified but should get you going in a direction. For complex validation, you can still validation using ValidationRule objects you create and then just check the IsValid property of the ValidationResult object returned.
// NOTE: The enum member name matches the name of the property bound to each textbox
public enum ControlIDs
{
TextBox1Value = 0,
TextBox2Value
}
public class MyViewModel : IDataErrorInfo
{
private readonly Dictionary<ControlIDs, string> errors;
public string ErrorText
{
get
{
if (errors.ContainsKey(ControlIDs.TextBox1) && errors.ContainsKey(ControlIDs.TextBox2))
{
return "Errors: " + errors[ControlsIDs.TextBox1] + ", " + errors[ControlsIDs.TextBox2];
}
if (errors.ContainsKey(ControlIDs.TextBox1))
{
return "Error: " + errors[ControlsIDs.TextBox1];
}
if (errors.ContainsKey(ControlIDs.TextBox2))
{
return "Error: " + errors[ControlsIDs.TextBox2];
}
}
}
public MyViewModel()
{
errors = new Dictionary<ControlIDs, string>();
}
private void UpdateErrorCollection(ControlIDs fieldKey, string error)
{
if (errors.ContainsKey(fieldKey))
{
errors[fieldKey] = error;
}
else
{
errors.Add(fieldKey, error);
}
OnPropertyChanged("ErrorText");
}
#region IDataErrorInfo
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
string error = string.Empty;
if (columnName == ControlIDs.TextBox1Value.ToString())
{
if (string.IsNullOrWhiteSpace(TextBox1Value))
{
error = "TextBox1 must contain a value";
UpdateErrorCollection(ControlIDs.TextBox1Value, error);
}
else
{
errors.Remove(ControlIDs.TextBox1Value);
}
}
else if (columnName == ControlIDs.TextBox2Value))
{
if (string.IsNullOrWhiteSpace(TextBox2Value))
{
error = "TextBox2 must contain a value";
UpdateErrorCollection(ControlIDs.TextBox2Value, error);
}
else
{
errors.Remove(ControlIDs.TextBox2Value);
}
}
// returning null indicates success
return !string.IsNullOrWhiteSpace(error) ? error : null;
}
#endregion
}
Now just bind your StatusBarItem TextBlock to the ErrorText property.

WPF List View Sort on Load

Here is the problem:
I want to sort a ListView when it is first loaded.
I have implemented the functionality where in the List View can be sorted if the Header columns
in the ListView are clicked.
I unable to find a suitable event which I can use to call my sort function.
I tried using OnInitialized of the UserControl and Loaded events but it seems the List View is
not populated when I call these functions.
I tried GotFocus of ListView. It works but then I have to click on the window to get the sorting done.
I want the sorting to be done as soon as the ListView is loaded.
I am using XML data binding with the List View.
The ListView is part of a UserControl. The User Control is hosted in a MMC app.
Please let me know if you need any other information.
public class SortableGridViewColumn : GridViewColumn
{
public string SortPropertyName
{
get { return (string)GetValue(SortPropertyNameProperty); }
set { SetValue(SortPropertyNameProperty, value); }
}
// Using a DependencyProperty as the backing store for SortPropertyName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SortPropertyNameProperty =
DependencyProperty.Register("SortPropertyName", typeof(string),
typeof(SortableGridViewColumn), new UIPropertyMetadata(""));
public bool IsDefaultSortColumn
{
get { return (bool)GetValue(IsDefaultSortColumnProperty); }
set { SetValue(IsDefaultSortColumnProperty, value); }
}
public static readonly DependencyProperty IsDefaultSortColumnProperty =
DependencyProperty.Register("IsDefaultSortColumn", typeof(bool),
typeof(SortableGridViewColumn), new UIPropertyMetadata(false));
}
public class SortableListView : ListView
{
public SortableListView()
{
}
SortableGridViewColumn lastSortedOnColumn = null;
ListSortDirection lastDirection = ListSortDirection.Ascending;
public void Sort(string sortBy, ListSortDirection direction)
{
ICollectionView dataView = CollectionViewSource.GetDefaultView
(this.ItemsSource);
//Check if dataView isn't null
if (dataView != null)
{
dataView.SortDescriptions.Clear();
SortDescription sd1 = new SortDescription("#isenabled", direction);
dataView.SortDescriptions.Add(sd1);
SortDescription sd = new SortDescription(sortBy, direction);
dataView.SortDescriptions.Add(sd);
dataView.Refresh();
}
}
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
if (headerClicked != null &&
headerClicked.Role != GridViewColumnHeaderRole.Padding)
{
// attempt to cast to the sortableGridViewColumn object.
SortableGridViewColumn sortableGridViewColumn = (headerClicked.Column) as SortableGridViewColumn;
// ensure that the column header is the correct type and a sort property has been set.
if (sortableGridViewColumn != null && !String.IsNullOrEmpty(sortableGridViewColumn.SortPropertyName))
{
ListSortDirection direction;
bool newSortColumn = false;
// determine if this is a new sort, or a switch in sort direction.
if (lastSortedOnColumn == null
|| String.IsNullOrEmpty(lastSortedOnColumn.SortPropertyName)
|| !String.Equals(sortableGridViewColumn.SortPropertyName, lastSortedOnColumn.SortPropertyName, StringComparison.InvariantCultureIgnoreCase))
{
newSortColumn = true;
direction = ListSortDirection.Ascending;
}
else
{
if (lastDirection == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
}
else
{
direction = ListSortDirection.Ascending;
}
}
// get the sort property name from the column's information.
string sortPropertyName = sortableGridViewColumn.SortPropertyName;
// Sort the data.
Sort(sortPropertyName, direction);
lastSortedOnColumn = sortableGridViewColumn;
lastDirection = direction;
}
}
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// add the event handler to the GridViewColumnHeader. This strongly ties this ListView to a GridView.
this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClickedHandler));
// cast the ListView's View to a GridView
GridView gridView = this.View as GridView;
if (gridView != null)
{
// determine which column is marked as IsDefaultSortColumn. Stops on the first column marked this way.1
SortableGridViewColumn sortableGridViewColumn = null;
foreach (GridViewColumn gridViewColumn in gridView.Columns)
{
sortableGridViewColumn = gridViewColumn as SortableGridViewColumn;
if (sortableGridViewColumn != null)
{
if (sortableGridViewColumn.IsDefaultSortColumn)
{
break;
}
sortableGridViewColumn = null;
}
}
// if the default sort column is defined, sort the data
if (sortableGridViewColumn != null)
{
lastSortedOnColumn = sortableGridViewColumn;
Sort(sortableGridViewColumn.SortPropertyName, ListSortDirection.Ascending);
}
}
}
}
The XAML is as shown below:
**<local:SortableListView x:Name="ListViewControl" Grid.Row="0" ItemContainerStyle="{DynamicResource StretchedContainerStyle}"
ItemTemplateSelector="{DynamicResource myControlTemplateSelector}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource dataProvider},
XPath=//CONFIGURATION}">
<ListView.View >
<GridView >
<local:SortableGridViewColumn Header="ID" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#id}"
IsDefaultSortColumn="True"
SortPropertyName="#id"/>
<local:SortableGridViewColumn Header="VALUE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
CellTemplateSelector="{DynamicResource myControlTemplateSelector}"
SortPropertyName="#value"/>
<local:SortableGridViewColumn Header="DATATYPE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#data_type}"
SortPropertyName="#data_type"/>
<local:SortableGridViewColumn Header="DESCRIPTION" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#description}"
SortPropertyName="#description"
Width="{Binding ElementName=ListViewControl, Path=ActualWidth}"/>
</GridView>
</ListView.View>
</local:SortableListView>**
<StackPanel Grid.Row="1">
<Button Grid.Row="1" HorizontalAlignment="Stretch" Height="34" HorizontalContentAlignment="Stretch" >
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Orientation="Horizontal" FlowDirection="RightToLeft" Height="30">
<Button Grid.Row="1" Content ="Apply" Padding="0,0,0,0 " Margin="6,2,0,2" Name="btn_Apply" HorizontalAlignment="Right" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Width="132" IsTabStop="True" Click="btn_ApplyClick" Height="24" />
</StackPanel >
</Button>
</StackPanel >
</Grid>
I finally was able to resolve it.
I had to use the Converter on ListView ItemSource. And then sort the List on Convert Function.
Here is the code below:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;
using System.Threading;
using System.Collections.ObjectModel;
namespace ...MiscellaneousCAESettings
{
/// <summary>
/// Interaction logic for ConfigDataView.xaml
/// </summary>
public partial class ConfigDataView : UserControl, IConfigDataViewControl
{
ConfigDataViewPresenter _presenter = null;
public static string _currDataType = "";
public static string _min = "" ;
public static string _max = "";
public string Min
{
get
{
return _min;
}
set
{
_min = value ;
}
}
public string Max
{
get
{
return _max;
}
set
{
_max = value;
}
}
public string CurrDataType
{
get
{
return _currDataType;
}
set
{
_currDataType = value;
}
}
public ConfigDataView()
{
InitializeComponent();
//To give the classic windows look
Uri uri = new Uri("PresentationFramework.Classic;V3.0.0.0;31bf3856ad364e35;component\\themes/classic.xaml", UriKind.Relative);
this.Resources.MergedDictionaries.Add(Application.LoadComponent(uri) as ResourceDictionary);
}
private void txtBoxGotFocus(object sender, RoutedEventArgs e)
{
Min = "" ;
Max = "" ;
TextBox txtbox = e.Source as TextBox;
this.ListViewControl.SelectedItem = txtbox.DataContext;
//index
int index = this.ListViewControl.Items.IndexOf(this.ListViewControl.SelectedItem);
System.ComponentModel.ICollectionView dataView = CollectionViewSource.GetDefaultView(this.ListViewControl.ItemsSource);
object stCurr = (dataView.CurrentPosition ) ;
//Check if the "data_type" attribute exists
if (((XmlElement)dataView.CurrentItem).Attributes["data_type"] != null)
{
CurrDataType = ((XmlElement)dataView.CurrentItem).Attributes["data_type"].Value;
}
//Check if the "min" attribute exists
if (((XmlElement)dataView.CurrentItem).Attributes["min"] != null)
{
Min = ((XmlElement)dataView.CurrentItem).Attributes["min"].Value;
}
//Check if the "min" attribute exists
if (((XmlElement)dataView.CurrentItem).Attributes["max"] != null)
{
Max = ((XmlElement)dataView.CurrentItem).Attributes["max"].Value;
}
}
#region IConfigDataViewControl Members
public void LoadRootConfigData(string xmlFileName, string xmlFileContent, string xmlXPath)
{
try
{
XmlDocument configFileDoc = new XmlDocument();
configFileDoc.LoadXml(xmlFileContent);
XmlDataProvider xmldp = (XmlDataProvider)this.TryFindResource("dataProvider");
xmldp.Document = configFileDoc;
if (string.IsNullOrEmpty(xmlXPath))
{
xmldp.XPath = #"//node()[1]/node()[#value]";
}
else
{
xmldp.XPath = xmlXPath;
}
Binding bnd = new Binding();
bnd.Source = xmldp;
bnd.Converter = new SortList();
ListViewControl.SetBinding(ItemsControl.ItemsSourceProperty, bnd);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void LoadCategoryConfigData(string xmlFile, string xmlFileContent, string CategoryNodeName)
{
try
{
XmlDocument configFileDoc = new XmlDocument();
configFileDoc.LoadXml(xmlFileContent);
XmlDataProvider xmldp = (XmlDataProvider)this.TryFindResource("dataProvider");
xmldp.Document = configFileDoc;
xmldp.XPath = #"//CONTEXT[#id='" + CategoryNodeName + #"']/CONFIGURATION";
Binding bnd = new Binding();
bnd.Source = xmldp;
bnd.Converter = new SortList();
ListViewControl.SetBinding(ItemsControl.ItemsSourceProperty, bnd);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void AttachPresenter(ConfigDataViewPresenter cfgpresenter)
{
_presenter = cfgpresenter;
}
#endregion
private void btn_ApplyClick(object sender, RoutedEventArgs e)
{
XmlDataProvider odp = (XmlDataProvider)this.TryFindResource("dataProvider");
XmlDocument configFileDoc = new XmlDocument();
configFileDoc =odp.Document;
_presenter.Save(configFileDoc.InnerXml );
}
}
public class TextBoxMinMaxValidation : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
try
{
//Check for min max string length if it is a "Text" data type
if (ConfigDataView._currDataType.ToLower() == "text")
{
int minLength = Convert.ToInt32(ConfigDataView._min);
int maxLength = Convert.ToInt32(ConfigDataView._max);
int strLength = value.ToString().Length;
bool isValidLength = true;
isValidLength = ((strLength >= minLength) && (strLength <= maxLength));
if (!isValidLength)
{
return new ValidationResult(false, string.Format("The input String Length is out of range. The String Length should be between {0} to {1}", minLength, maxLength));
}
else
{
return new ValidationResult(true, null);
}
}
//Check for min max string length if it is a "Numeric" data type
if (ConfigDataView._currDataType.ToLower() != "numeric")
{
return new ValidationResult(true, null);
}
int min = Convert.ToInt32(ConfigDataView._min);
int max = Convert.ToInt32(ConfigDataView._max);
int res ;
bool isNumber = int.TryParse(value.ToString(), out res);
bool isValidRange = true;
if (!isNumber)
{
return new ValidationResult(false, "The input string is in incorrect format. Should be a Number.");
}
isValidRange = ((res >= min) && (res <= max));
if (!isValidRange)
{
return new ValidationResult(false, string.Format("The input integer is out of range. The number should be between {0} to {1}", min, max));
}
}
catch
{
}
return new ValidationResult(true, null);
}
}
public class ControlTemplateSelector : DataTemplateSelector
{
public const String XML_TAG_DATATYPE = "data_type";
public const String DATATYPE_DROPDOWN = "Dropdown";
public const String DATATYPE_BOOLEAN = "Boolean";
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
FrameworkElement window = (container as FrameworkElement);
try
{
XmlNode node = (XmlNode)item;
String dataType = "";
if (node.Attributes[XML_TAG_DATATYPE] != null)
{
dataType = (string)node.Attributes.GetNamedItem(XML_TAG_DATATYPE).Value;
}
if (dataType == DATATYPE_DROPDOWN)
{
return window.FindResource("dropDownTemplate") as DataTemplate;
}
if (dataType == DATATYPE_BOOLEAN)
{
return window.FindResource("booldropDownTemplate") as DataTemplate;
}
}
catch (Exception ex)
{
MessageBox.Show("Select template Exception" + ex.Message );
}
return window.FindResource("textTemplate") as DataTemplate;
}
}
public class boolConverter : IValueConverter
{
public const String XML_TAG_VALUE = "value";
public const String XML_TAG_ID = "id";
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Boolean boolVal = false;
try
{
boolVal = System.Convert.ToBoolean(value);
}
catch
{
string strVal = value.ToString();
int iVal = int.Parse(strVal);
boolVal = System.Convert.ToBoolean(iVal);
}
if (boolVal == true)
{
return 1;
}
else
{
return 0;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Boolean boolVal = false;
try
{
boolVal = System.Convert.ToBoolean(value);
}
catch
{
string strVal = value.ToString();
int iVal = int.Parse(strVal);
boolVal = System.Convert.ToBoolean(iVal);
}
return boolVal;
}
}
public class SortableGridViewColumn : GridViewColumn
{
public string SortPropertyName
{
get { return (string)GetValue(SortPropertyNameProperty); }
set { SetValue(SortPropertyNameProperty, value); }
}
// Using a DependencyProperty as the backing store for SortPropertyName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SortPropertyNameProperty =
DependencyProperty.Register("SortPropertyName", typeof(string),
typeof(SortableGridViewColumn), new UIPropertyMetadata(""));
public bool IsDefaultSortColumn
{
get { return (bool)GetValue(IsDefaultSortColumnProperty); }
set { SetValue(IsDefaultSortColumnProperty, value); }
}
public static readonly DependencyProperty IsDefaultSortColumnProperty =
DependencyProperty.Register("IsDefaultSortColumn", typeof(bool),
typeof(SortableGridViewColumn), new UIPropertyMetadata(false));
}
public class SortableListView : ListView
{
public SortableListView()
{
// add the event handler to the GridViewColumnHeader. This strongly ties this ListView to a GridView.
this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(GridViewColumnHeaderClickedHandler));
}
SortableGridViewColumn lastSortedOnColumn = null;
ListSortDirection lastDirection = ListSortDirection.Ascending;
public void Sort(string sortBy, ListSortDirection direction)
{
ICollectionView dataView = CollectionViewSource.GetDefaultView
(this.ItemsSource);
//Check if dataView isn't null
if (dataView != null)
{
dataView.SortDescriptions.Clear();
SortDescription sd1 = new SortDescription("#isenabled", direction);
dataView.SortDescriptions.Add(sd1);
SortDescription sd = new SortDescription(sortBy, direction);
dataView.SortDescriptions.Add(sd);
dataView.Refresh();
}
}
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
{
GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
if (headerClicked != null &&
headerClicked.Role != GridViewColumnHeaderRole.Padding)
{
// attempt to cast to the sortableGridViewColumn object.
SortableGridViewColumn sortableGridViewColumn = (headerClicked.Column) as SortableGridViewColumn;
// ensure that the column header is the correct type and a sort property has been set.
if (sortableGridViewColumn != null && !String.IsNullOrEmpty(sortableGridViewColumn.SortPropertyName))
{
ListSortDirection direction;
// determine if this is a new sort, or a switch in sort direction.
if (lastSortedOnColumn == null
|| String.IsNullOrEmpty(lastSortedOnColumn.SortPropertyName)
|| !String.Equals(sortableGridViewColumn.SortPropertyName, lastSortedOnColumn.SortPropertyName, StringComparison.InvariantCultureIgnoreCase))
{
direction = ListSortDirection.Descending;
}
else
{
if (lastDirection == ListSortDirection.Ascending)
{
direction = ListSortDirection.Descending;
}
else
{
direction = ListSortDirection.Ascending;
}
}
// get the sort property name from the column's information.
string sortPropertyName = sortableGridViewColumn.SortPropertyName;
// Sort the data.
Sort(sortPropertyName, direction);
lastSortedOnColumn = sortableGridViewColumn;
lastDirection = direction;
}
}
}
}
public class SortList : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//If the value is null tell binding engine to do nothing
if (value == null)
{
return Binding.DoNothing;
}
ListCollectionView view = (ListCollectionView)
CollectionViewSource.GetDefaultView(value);
SortDescription sort_isdisabled =
new SortDescription("#isenabled",
ListSortDirection.Ascending);
view.SortDescriptions.Add(sort_isdisabled);
SortDescription sort_id =
new SortDescription("#id",
ListSortDirection.Ascending);
view.SortDescriptions.Add(sort_id);
return view;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}
}
The XAML is as follows:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:.....MiscellaneousCAESettings"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" >
<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>
<DataTemplate x:Key="textTemplate">
<TextBox HorizontalAlignment= "Stretch"
IsEnabled="{Binding XPath=./#isenabled}"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
GotFocus="txtBoxGotFocus"
Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding XPath="./#value" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:TextBoxMinMaxValidation>
<local:TextBoxMinMaxValidation.DataType>
<local:DataTypeCheck
Datatype="{Binding Source={StaticResource dataProvider}, XPath='/[#id=CustomerServiceQueueName]'}"/>
</local:TextBoxMinMaxValidation.DataType>
<local:TextBoxMinMaxValidation.ValidRange>
<local:Int32RangeChecker
Minimum="{Binding Source={StaticResource dataProvider}, XPath=./#min}"
Maximum="{Binding Source={StaticResource dataProvider}, XPath=./#max}"/>
</local:TextBoxMinMaxValidation.ValidRange>
</local:TextBoxMinMaxValidation>
</Binding.ValidationRules>
</Binding >
</TextBox.Text>
</TextBox>
</DataTemplate>
<DataTemplate x:Key="dropDownTemplate">
<ComboBox Name="cmbBox" HorizontalAlignment="Stretch"
SelectedIndex="{Binding XPath=./#value}"
ItemsSource="{Binding XPath=.//OPTION/#value}"
IsEnabled="{Binding XPath=./#isenabled}"
/>
</DataTemplate>
<DataTemplate x:Key="booldropDownTemplate">
<ComboBox Name="cmbBox" HorizontalAlignment="Stretch"
SelectedIndex="{Binding XPath=./#value, Converter={StaticResource boolconvert}}">
<ComboBoxItem>True</ComboBoxItem>
<ComboBoxItem>False</ComboBoxItem>
</ComboBox>
</DataTemplate>
<local:ControlTemplateSelector
x:Key="myControlTemplateSelector"/>
<Style x:Key="StretchedContainerStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template" Value="{DynamicResource ListBoxItemControlTemplate1}"/>
</Style>
<ControlTemplate x:Key="ListBoxItemControlTemplate1" TargetType="{x:Type ListBoxItem}">
<Border SnapsToDevicePixels="true" x:Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" Padding="{TemplateBinding Padding}" BorderThickness="0,0.5,0,0.5">
<GridViewRowPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</ControlTemplate>
<Style x:Key="CustomHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Background" Value="LightGray" />
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="Padding" Value="2,0,2,0"/>
</Style>
</UserControl.Resources>
<Grid x:Name="GridViewControl" Height="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="34"/>
</Grid.RowDefinitions>
<ListView x:Name="ListViewControl" Grid.Row="0" ItemContainerStyle="{DynamicResource StretchedContainerStyle}"
ItemTemplateSelector="{DynamicResource myControlTemplateSelector}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Source={StaticResource dataProvider},
XPath=//CONFIGURATION}">
<ListView.View >
<GridView >
<GridViewColumn Header="ID" HeaderContainerStyle="{StaticResource CustomHeaderStyle}" DisplayMemberBinding="{Binding XPath=./#id}"/>
<GridViewColumn Header="VALUE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}" CellTemplateSelector="{DynamicResource myControlTemplateSelector}" />
<GridViewColumn Header="DATATYPE" HeaderContainerStyle="{StaticResource CustomHeaderStyle}" DisplayMemberBinding="{Binding XPath=./#data_type}"/>
<GridViewColumn Header="DESCRIPTION" HeaderContainerStyle="{StaticResource CustomHeaderStyle}"
DisplayMemberBinding="{Binding XPath=./#description}"
Width="{Binding ElementName=ListViewControl, Path=ActualWidth}"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Row="1">
<Button Grid.Row="1" HorizontalAlignment="Stretch" Height="34" HorizontalContentAlignment="Stretch" >
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Orientation="Horizontal" FlowDirection="RightToLeft" Height="30">
<Button Grid.Row="1" Content ="Apply" Padding="0,0,0,0 " Margin="6,2,0,2" Name="btn_Apply" HorizontalAlignment="Right" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Width="132" IsTabStop="True" Click="btn_ApplyClick" Height="24" />
</StackPanel >
</Button>
</StackPanel >
</Grid>

Resources