Skip to content

Instantly share code, notes, and snippets.

@edchapel
Created April 19, 2011 23:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edchapel/929974 to your computer and use it in GitHub Desktop.
Save edchapel/929974 to your computer and use it in GitHub Desktop.
public partial class ConvictionHistory
{
[RegularExpression(@"^\d*$", ErrorMessage = "Felonies must be a number.")]
public string FeloniesText
{
get { return this.GetNumberAsString(_ => _.Felonies); }
set { this.SetIntProperty(_ => _.Felonies, value, number => Felonies = number); }
}
[RegularExpression(@"^\d*$", ErrorMessage = "Misdemeanors must be a number.")]
public string MisdemeanorsText
{
get { return this.GetNumberAsString(_ => _.Misdemeanors); }
set { this.SetIntProperty(_ => _.Misdemeanors, value, number => Misdemeanors = number); }
}
[RegularExpression(@"^\d*\.?\d*$", ErrorMessage = "Bail must be a number.")]
public string BailText
{
get { return this.GetNumberAsString(_ => _.Bail ); }
set { this.SetDoubleProperty(_ => _.Bail , value, number => Bail = number); }
}
#region Text-Number Support Code
private IDictionary<string, string> _textProperties;
public IDictionary<string, string> TextProperties
{
get { return _textProperties ?? (_textProperties = new Dictionary<string, string>()); }
}
void IExposeNumbersAsText.ValidateProperty(string propertyName, object value)
{
ValidateProperty(propertyName, value);
}
void IRaisesPropertyChangedEvent.RaisePropertyChanged(PropertyChangedEventArgs args)
{
RaisePropertyChanged(args.PropertyName);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (TextProperties.ContainsKey(e.PropertyName))
{
RaisePropertyChanged(e.PropertyName + "Text");
}
}
#endregion
}
/// <summary>
/// Supports presenting and validating numbers within TextBoxes.
/// </summary>
public static class ExtensionsForNumbersWithTextboxes
{
/// <summary>
/// Returns the last value set on the <paramref name="numericProperty"/> from the TextBox.
/// </summary>
/// <param name="entity">The Entity with a numeric property to be bound to a TextBox.</param>
/// <param name="numericProperty">A lambda representing the path to the numeric property.</param>
/// <typeparam name="TEntity">The type hosting the numeric property you are battling to present in a TextBox.</typeparam>
/// <typeparam name="TNumber">Usually an int or double.</typeparam>
/// <returns>A string version of the numeric property or the last invalid text set by the user.</returns>
public static string GetNumberAsString<TEntity, TNumber>(this TEntity entity, Expression<Func<TEntity, TNumber?>> numericProperty)
where TEntity : class, IExposeNumbersAsText
where TNumber : struct
{
var number = numericProperty.GetValueFrom(entity);
var propName = numericProperty.GetNameFor();
string text;
entity.TextProperties.TryGetValue(propName, out text);
return text == null && number.HasValue ? number.Value.ToString() : text;
}
/// <summary>
/// Stores the <param name="value"/> set in the TextBox in the <param name="entity"/>.
/// An invalid <param name="value"/> is stored in the <see cref="IExposeNumbersAsText.TextProperties"/> for later.
/// A valid <param name="value"/> is sent to the <param name="setIntProperty"/> to be stored in the original numeric property.
/// </summary>
/// <param name="entity">The host object with an numeric property.</param>
/// <param name="intProperty">A lambda representing the path to the numeric property.</param>
/// <param name="value">The string value set against the text version of the property.</param>
/// <param name="setIntProperty">A lambda to set the numeric property with an int (or null) when the value from the TextBox is valid.</param>
/// <typeparam name="TEntity">The type hosting the numeric property you are battling to present in a TextBox.</typeparam>
public static void SetIntProperty<TEntity>(this TEntity entity, Expression<Func<TEntity, int?>> intProperty, string value, Action<int?> setIntProperty)
where TEntity : class, IExposeNumbersAsText
{
SetStringToNumeric(entity, intProperty, value, setIntProperty,
str =>
{
int number;
return int.TryParse(str, out number) ? (int?) number : null;
});
}
/// <summary>
/// Stores the <param name="value"/> set in the TextBox in the <param name="entity"/>.
/// An invalid <param name="value"/> is stored in the <see cref="IExposeNumbersAsText.TextProperties"/> for later.
/// A valid <param name="value"/> is sent to the <param name="setDoubleProperty"/> to be stored in the original numeric property.
/// </summary>
/// <param name="entity">The host object with an numeric property.</param>
/// <param name="doubleProperty">A lambda representing the path to the numeric property.</param>
/// <param name="value">The string value set against the text version of the property.</param>
/// <param name="setDoubleProperty">A lambda to set the numeric property with an double (or null) when the value from the TextBox is valid.</param>
/// <typeparam name="TEntity">The type hosting the numeric property you are battling to present in a TextBox.</typeparam>
public static void SetDoubleProperty<TEntity>(this TEntity entity, Expression<Func<TEntity, double?>> doubleProperty, string value, Action<double?> setDoubleProperty)
where TEntity : class, IExposeNumbersAsText
{
SetStringToNumeric(entity, doubleProperty, value, setDoubleProperty,
str =>
{
double number;
return double.TryParse(str, out number) ? (double?) number : null;
});
}
private static void SetStringToNumeric<TEntity, TNumber>(TEntity entity,
Expression<Func<TEntity, TNumber?>> numericProperty,
string value, Action<TNumber?> setNumericProperty,
Func<string, TNumber?> parseNumber)
where TEntity : class, IExposeNumbersAsText
where TNumber : struct
{
// Need the name of the property
var propName = numericProperty.GetNameFor();
// Tell the WCF RIA Entity to run validation. This will update a TextBox with a validation error to no longer be red.
entity.ValidateProperty(propName + "Text", value);
// Store empty strings as nullable numerics
if (string.IsNullOrWhiteSpace(value))
{
entity.TextProperties[propName] = null;
setNumericProperty(null);
return;
}
// Call the int/double specific parser
var number = parseNumber(value);
if (number.HasValue)
{
// The user typed a valid number
entity.TextProperties[propName] = null;
setNumericProperty(number.Value);
}
else
{
// The value was not valid. Store it in the entity's storage area. It will be retrieved by validation
// when the entity is submitted to the server.
entity.TextProperties[propName] = value;
// Raise a property changed for the text version of the property for any listeners to react
entity.RaisePropertyChanged(new PropertyChangedEventArgs(propName + "Text"));
}
}
}
/// <summary>
/// <para>Assists the <see cref="ExtensionsForNumbersWithTextboxes"/> extension to provide a solution for
/// presenting and validating numbers in TextBoxes.</para>
/// <para></para>
/// </summary>
public interface IExposeNumbersAsText : IRaisesPropertyChangedEvent
{
/// <summary>
/// Storage for the string-based state of a numeric property.
/// </summary>
IDictionary<string, string> TextProperties { get; }
/// <summary>
/// Exposes the <see cref="Entity"/>'s <see cref="Entity.ValidateProperty"/> method.
/// </summary>
/// <param name="propertyName">Name of the property</param>
/// <param name="value">Value of the property</param>
void ValidateProperty(string propertyName, object value);
}
///<summary>
/// Extends <see cref="INotifyPropertyChanged"/> to support the
/// <see cref="NotifyPropertyChangedExtensions.NotifyIfPropertyChanged{THost,TProperty}"/> extension method.
///</summary>
public interface IRaisesPropertyChangedEvent : INotifyPropertyChanged
{
///<summary>
/// Raise the <see cref="INotifyPropertyChanged.PropertyChanged"/> event.
///</summary>
///<param name="args">The args with the property name defined</param>
void RaisePropertyChanged(PropertyChangedEventArgs args);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment