// Copyright (c) 2010 - 2012, Andreas Ganzer. All Rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using Ganzer.Validation;
namespace Ganzer.Wpf.Validation
{
//############################################################################
/// <summary>
/// The DatePickerLink class defines a link for <see cref="DatePicker"/> controls.
/// </summary>
///
/// <seealso cref="Validator"/>
///
public class DatePickerLink : ValidatorLink
{
#region types
/// <summary>
/// The TextInfo class encapsulates text settings for pending changings.
/// </summary>
///
protected class TextInfo
{
#region properties
/// <summary>
/// Gets the start index of the selection.
/// </summary>
///
/// <returns>The start index.</returns>
///
public int SelectionStart
{
get;
private set;
}
/// <summary>
/// Gets the length of the selection.
/// </summary>
///
/// <returns>The length index.</returns>
///
public int SelectionLength
{
get;
private set;
}
/// <summary>
/// Gets the text to set.
/// </summary>
///
/// <returns>The text to set.</returns>
///
public string Text
{
get;
private set;
}
#endregion
#region initialization
/// <summary>
/// Initializes this object with the specifeid arguments.
/// </summary>
///
/// <param name="text">The text to set.</param>
/// <param name="selStart">The start of the selection.</param>
/// <param name="selLength">The length of the selection.</param>
///
public TextInfo( string text, int selStart, int selLength )
{
Text = text ?? string.Empty;
if( selStart < 0 )
SelectionStart = 0;
else if( selStart > Text.Length )
SelectionStart = Text.Length;
else
SelectionStart = selStart;
if( selLength < 0 )
SelectionLength = 0;
else if( SelectionStart + selLength > Text.Length )
SelectionLength = Text.Length - SelectionStart;
else
SelectionLength = selLength;
}
#endregion
}
#endregion
#region fields
private bool __preventTextChangeValidation;
#endregion
#region properties
/// <summary>
/// Gets or sets the line editor control that is linked with the validator.
/// </summary>
///
/// <returns><see cref="Control">Control</see> as <see cref="DatePicker"/>.
/// </returns>
///
/// <remarks>
/// If this property is <c>null</c> <see cref="ValidatorLink.Validate()">Validate()</see>
/// does always return <c>true.</c>
/// </remarks>
///
/// <seealso cref="Control"/>
/// <seealso cref="ValidatorLink.Validator">Validator</seealso>
///
public DatePicker DatePicker
{
get
{
Debug.Assert(Control == null || Control is DatePicker);
return Control as DatePicker;
}
set
{
this.Control = value;
}
}
/// <summary>
/// Gets or sets the control that is linked with the validator.
/// </summary>
///
/// <returns>The control to validate.</returns>
///
/// <exception cref="ArgumentException">The value to set is not of type
/// <see cref="DatePicker"/> and is not <c>null</c>.</exception>
///
/// <remarks>
/// If this property is <c>null</c> <see cref="ValidatorLink.Validate()">Validate()</see>
/// does always return <c>true.</c>
/// </remarks>
///
/// <seealso cref="ValidatorLink.Validator">Validator</seealso>
///
public override IInputElement Control
{
get
{
return base.Control;
}
set
{
Debug.Assert(value == null || value is DatePicker);
if( value != null && !(value is DatePicker) )
throw new ArgumentException("The specified value must be of type DatePicker.", "value");
if( value == base.Control )
return;
if( DatePicker != null )
DatePicker.Loaded -= Control_Loaded;
base.Control = value;
if( DatePicker != null )
DatePicker.Loaded += Control_Loaded;
}
}
/// <summary>
/// Gets or sets the pending text info.
/// </summary>
///
/// <returns>The pending info or <c>null</c> if no info is pending.
/// </returns>
///
/// <remarks>
/// This must be queried and resetted wihtin the text change event of
/// the control.
/// </remarks>
///
protected TextInfo PendingTextInfo
{
get;
set;
}
/// <summary>
/// This is used by <see cref="ValidatorLink.Text"/> and returns the text to validate.
/// </summary>
///
/// <returns>The text content of <see cref="Control"/>.</returns>
///
protected override string ControlText
{
get
{
if( PickerTextBox == null )
return DatePicker.Text;
int selStart = PickerTextBox.SelectionStart;
int selLength = PickerTextBox.SelectionLength;
PickerTextBox.SelectAll();
string text = PickerTextBox.SelectedText;
PickerTextBox.Select(selStart, selLength);
return text;
}
}
/// <summary>
/// This is used by <see cref="ValidatorLink.BindingExpression"/> and returns
/// the binding expression that is used to bind the text of the control.
/// </summary>
///
/// <returns>The used expression or <c>null</c> if the text is not bound.</returns>
///
protected override BindingExpressionBase CurrentBindingExpression
{
get
{
return BindingOperations.GetBindingExpressionBase(DatePicker, DatePicker.TextProperty);
}
}
/// <summary>
/// This is used by <see cref="ValidatorLink.Binding"/> and returns the binding
/// that is used to bind the text of the control.
/// </summary>
///
/// <returns>The used binding or <c>null</c> if the text is not bound.</returns>
///
protected override BindingBase CurrentBinding
{
get
{
return BindingOperations.GetBindingBase(DatePicker, DatePicker.TextProperty);
}
}
private TextBox _textBox;
/// <summary>
/// Gets the text box of the linked control.
/// </summary>
///
/// <returns>The text box of the linked control or <c>null</c> if the control
/// is not loaded yet.</returns>
///
private TextBox PickerTextBox
{
get
{
if( _textBox == null )
_textBox = DatePicker.Template.FindName("PART_TextBox", DatePicker) as TextBox;
return _textBox;
}
}
#endregion
#region ctor/dtor
/// <summary>
/// Initializes this object with default values.
/// </summary>
///
public DatePickerLink()
{
}
/// <summary>
/// Links the specifed validator with the specifed control.
/// </summary>
///
/// <param name="control">The control to link with the given validator.</param>
/// <param name="validator">The validator to link with the given control.</param>
/// <param name="options">The options to set.</param>
///
public DatePickerLink( DatePicker control, Validator validator, LinkOptions options )
: base(control, validator, (IInputElement)null, options)
{
if( DatePicker != null )
DatePicker.Loaded += Control_Loaded;
}
/// <summary>
/// Links the specifed validator with the specifed control.
/// </summary>
///
/// <param name="control">The control to link with the given validator.</param>
/// <param name="validator">The validator to link with the given control.</param>
/// <param name="ignored">The control to ignore. This is used only if the
/// validator contains the option <see cref="LinkOptions.ForceValid"/>.
/// In this case the controls text is not validated if the input focus is set
/// to the control given in <c>ignored</c>.</param>
/// <param name="options">The options to set.</param>
///
public DatePickerLink( DatePicker control, Validator validator, IInputElement ignored, LinkOptions options )
: base(control, validator, ignored, options)
{
if( DatePicker != null )
DatePicker.Loaded += Control_Loaded;
}
/// <summary>
/// Links the specifed validator with the specifed control.
/// </summary>
///
/// <param name="control">The control to link with the given validator.</param>
/// <param name="validator">The validator to link with the given control.</param>
/// <param name="ignored">The controls to ignore. This is used only if the
/// validator contains the option <see cref="LinkOptions.ForceValid"/>.
/// In this case the controls text is not validated if the input focus is set
/// to one of the control given in <c>ignored</c>.</param>
/// <param name="options">The options to set.</param>
///
public DatePickerLink( DatePicker control, Validator validator, IEnumerable<IInputElement> ignored, LinkOptions options )
: base(control, validator, ignored, options)
{
if( DatePicker != null )
DatePicker.Loaded += Control_Loaded;
}
#endregion
#region methods
/// <summary>
/// Validates the text of the control.
/// </summary>
///
/// <param name="x">This ist set to <c>null</c> if the text is valid. Otherwise
/// this ist set to an instance of a <see cref="ValidatorException"/> that
/// describes the error.</param>
///
/// <returns>Return <c>true</c> if the text of the control is valid,
/// otherwise <c>false</c> is returned.</returns>
///
protected override bool Validate( out ValidatorException x )
{
DateTimeValidator validator = Validator as DateTimeValidator;
string text = ControlText;
DateTime time;
if( validator == null || !DateTime.TryParse(text, out time) )
return Validator.Validate(text, CultureResolved, out x);
switch( validator.DateTimeType )
{
case DateTimeType.Date:
return Validator.Validate(time.ToString("d"), CultureResolved, out x);
case DateTimeType.ShortTime:
return Validator.Validate(time.ToString("t"), CultureResolved, out x);
case DateTimeType.LongTime:
return Validator.Validate(time.ToString("T"), CultureResolved, out x);
default:
return Validator.Validate(text, CultureResolved, out x);
}
}
/// <summary>
/// Selects the text of the control if possible.
/// </summary>
///
protected override void SelectAllText()
{
if( PickerTextBox != null )
PickerTextBox.SelectAll();
}
/// <summary>
/// Formats the text for displaying by calling <see cref="Validator.FormatText(string, TextFormat)"/>.
/// </summary>
///
/// <param name="frmt">How to format.</param>
///
protected override void FormatText( TextFormat frmt )
{
__preventTextChangeValidation = true;
try
{
DatePicker.Text = Validator.FormatText(ControlText, frmt, CultureResolved);
}
finally
{
__preventTextChangeValidation = false;
}
}
/// <summary>
/// Handles the input of the specified text.
/// </summary>
///
/// <param name="input">The text to insert into the control.</param>
///
/// <returns><c>true</c> when the input was valid; otherwise, <c>false</c> is
/// returned.</returns>
///
private bool HandleInput( string input )
{
Debug.Assert(PickerTextBox != null);
StringBuilder text = new StringBuilder(ControlText);
int selstart = PickerTextBox.SelectionStart;
int sellength = PickerTextBox.SelectionLength;
bool appending = selstart >= text.Length || selstart + sellength >= text.Length;
int maxlen = PickerTextBox.MaxLength;
if( maxlen <= 0 || text.Length < maxlen || sellength > 0 )
{
if( sellength > 0 )
text.Remove(selstart, sellength);
if( appending )
text.Append(input);
else
text.Insert(selstart, input);
if( Validator.IsValidInput(text, appending || HasOption(LinkOptions.FillAlways), CultureResolved) )
{
string txt = text.ToString();
if( maxlen > 0 && maxlen < txt.Length )
txt = txt.Substring(0, maxlen);
if( HasOption(LinkOptions.SelectFilled) )
PendingTextInfo = new TextInfo(txt, selstart + input.Length, text.Length);
else if( appending )
PendingTextInfo = new TextInfo(txt, text.Length, 0);
else
PendingTextInfo = new TextInfo(txt, selstart + input.Length, 0);
return true;
}
}
return false;
}
#endregion
#region event handling
/// <summary>
/// Called when the linked control is loaded. This registers the event
/// handlers.
/// </summary>
///
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
///
private void Control_Loaded( object sender, RoutedEventArgs e )
{
if( PickerTextBox != null )
{
PickerTextBox.PreviewKeyDown += TextBox_PreviewKeyDown;
PickerTextBox.PreviewTextInput += TextBox_PreviewTextInput;
PickerTextBox.TextChanged += TextBox_TextChanged;
}
}
/// <summary>
/// This is called automatically on a <c>KeyDown</c> event of the control.
/// This method sets <c>__preventTextChangeValidation</c>.
/// </summary>
///
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
///
private void TextBox_PreviewKeyDown( object sender, KeyEventArgs e )
{
if( Validator == null || PickerTextBox != sender || PickerTextBox.IsReadOnly || e.SystemKey != Key.None )
return;
switch( e.Key )
{
case Key.Delete:
if( ControlText.Length > PickerTextBox.SelectionStart )
__preventTextChangeValidation = true;
break;
case Key.Space:
if( !PickerTextBox.IsReadOnly )
e.Handled = !HandleInput(" ");
break;
}
}
/// <summary>
/// This is called automatically on a <c>KeyPress</c> event of the control.
/// The text of the control is validated by using <see cref="Validator.IsValidInput(StringBuilder, bool)"/>.
/// If the input is invalid the key is ignored. <c>e.Handled</c> is set to
/// <c>true</c> if the pressed key was inserted by this handler.
/// </summary>
///
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
///
private void TextBox_PreviewTextInput( object sender, TextCompositionEventArgs e )
{
if( Validator == null
|| PickerTextBox != sender
|| PickerTextBox.IsReadOnly
|| e.Handled
|| e.Text.Length == 0
|| (e.Text == "\t" && !PickerTextBox.AcceptsTab) )
{
return;
}
e.Handled = !HandleInput(e.Text);
}
/// <summary>
/// This is called if the text of the control is changed. This method validates
/// the text and formats it if it is valid.
/// </summary>
///
/// <param name="sender">The sender of the event.</param>
/// <param name="e">The event arguments.</param>
///
private void TextBox_TextChanged( object sender, EventArgs e )
{
if( PickerTextBox != sender )
return;
if( PendingTextInfo != null )
{
TextInfo info = PendingTextInfo;
PendingTextInfo = null;
__preventTextChangeValidation = true;
PickerTextBox.Text = info.Text;
//
// Adjusting the selection is necessary because
// a binding may change the text after is was set:
//
int selStart = info.SelectionStart;
string text = ControlText;
if( selStart > 0 && text.Length > info.Text.Length )
selStart += text.Length - info.Text.Length;
PickerTextBox.Select(selStart, info.SelectionLength);
}
else
{
try
{
if( Validator != null
&& HasOption(LinkOptions.TextChangeFormats)
&& FormatTextAllowed
&& !__preventTextChangeValidation )
{
ValidatorException x;
if( Validator.Validate(ControlText, CultureResolved, out x) )
FormatText(DatePicker.IsFocused ? TextFormat.Edit : TextFormat.Display);
}
}
finally
{
__preventTextChangeValidation = false;
}
}
}
#endregion
}
}