|
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows.Forms;
using Evaluant.Calculator;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Controls;
using System.Linq;
namespace Konamiman.TFS.Controls
{
/// <summary>
/// Custom control that shows the result of a calculation
/// that involves the value of one or more numeric fields of the work item.
/// </summary>
/// <remarks>
/// <para>
/// The definition for this control on the work item template needs an attribute named "expression" that details
/// how the calculation is performed. Numeric fields on the work item can be referenced as "[Name]" in the expression.
/// For example: <c><Control FieldName="MyFields.Exposure" Type="CalculationControl" Label="Exposure:" ReadOnly="True" expression="([Probability]*[Impact])/100"<</c>
/// </para>
/// <para>
/// If the control is associated to a numeric field, the calculated value is saved within the work item and can be displayed in work item queries.
/// </para>
/// <para>
/// In order to perform the calculation, an expression evaluator posted at The Code Project is used:
/// http://www.codeproject.com/KB/recipes/sota_expression_evaluator.aspx
/// (note that it depens on an external library, Evaluant.Antlr.dll)
/// </para>
/// </remarks>
public partial class ExpressionControl : UserControl, IWorkItemControl
{
#region Private fields
private decimal calculationValue;
Expression expressionObject = null;
private WorkItem workItem = null;
private Field[] workItemFields = null;
private StringDictionary properties = null;
private List<string> fieldsInExpression = null;
#endregion
#region Constructor
public ExpressionControl() : base()
{
InitializeComponent();
fieldsInExpression = new List<string>();
}
#endregion
#region Public properties and events
/// <summary>
/// Get and set the control attributes.
/// When setting, the expression is retrieved and the expression object created.
/// </summary>
StringDictionary IWorkItemControl.Properties
{
get
{
return properties;
}
set
{
properties = value;
fieldsInExpression.Clear();
if(properties == null) {
return;
}
var expression = properties["expression"];
if(string.IsNullOrEmpty(expression)) {
expression = properties["Expression"];
}
if(string.IsNullOrEmpty(expression)) {
expressionObject = null;
txtValue.Text = "(Expression not defined)";
return;
}
expressionObject = new Expression(expression);
expressionObject.EvaluateParameter += new EvaluateParameterHandler(expression_EvaluateParameter);
//We evaluate the expression once so that
//fieldsInExpression is appropriately filled.
try {
expressionObject.Evaluate();
}
catch(Exception) {
}
}
}
/// <summary>
/// Get and set the read only status for the control.
/// </summary>
bool IWorkItemControl.ReadOnly
{
get
{
return txtValue.ReadOnly;
}
set
{
txtValue.ReadOnly = value;
}
}
/// <summary>
/// Get or set the work item the control is contained in.
/// When setting, we capture the FieldChanged event of the work item.
/// </summary>
object IWorkItemControl.WorkItemDatasource
{
get
{
return workItem;
}
set
{
if(workItem != null) {
workItem.FieldChanged -= OnFieldChanged;
}
workItem = value as WorkItem;
if(workItem == null) {
workItemFields = new Field[0];
}
else {
workItemFields = new Field[workItem.Fields.Count];
((ICollection)workItem.Fields).CopyTo(workItemFields, 0);
}
if(workItem != null) {
workItem.FieldChanged += OnFieldChanged;
}
}
}
/// <summary>
/// Get or set the reference name of the field associated to the control.
/// </summary>
public string /*IWorkItemControl.*/WorkItemFieldName
{
get;
set;
}
public event EventHandler AfterUpdateDatasource;
public event EventHandler BeforeUpdateDatasource;
#endregion
#region Public methods
/// <summary>
/// Clear the control contents.
/// </summary>
void IWorkItemControl.Clear()
{
txtValue.Text = string.Empty;
}
/// <summary>
/// Apply the new control value to the associated field on the containing work item.
/// </summary>
void IWorkItemControl.FlushToDatasource()
{
if(workItem != null && WorkItemFieldName != null) {
var field = workItemFields.Where(f => f.ReferenceName == WorkItemFieldName).SingleOrDefault();
if(field != null) {
if(IsNumericType(field.FieldDefinition.SystemType)) {
SetFieldValue(field, calculationValue);
}
else if(field.FieldDefinition.SystemType == typeof(string)) {
SetFieldValue(field, calculationValue.ToString());
}
}
}
}
/// <summary>
/// Force the calculation of the control value.
/// </summary>
void IWorkItemControl.InvalidateDatasource()
{
txtValue.Text = GenerateNewTextboxValue();
}
void IWorkItemControl.SetSite(IServiceProvider serviceProvider)
{
}
#endregion
#region Private methods
/// <summary>
/// Apply a value to the associated field, firing the appropriate events.
/// </summary>
/// <param name="field"></param>
/// <param name="value"></param>
private void SetFieldValue(Field field, object value)
{
if(BeforeUpdateDatasource != null) {
BeforeUpdateDatasource(this, new EventArgs());
}
field.Value = value;
if(AfterUpdateDatasource != null) {
AfterUpdateDatasource(this, new EventArgs());
}
}
/// <summary>
/// This is invoked whenever the value of
/// any of the fields on the associated work item changes.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnFieldChanged(object sender, WorkItemEventArgs e)
{
//Beware: e.Field can be null!
if(e.Field != null && fieldsInExpression.Contains(e.Field.Name)) {
txtValue.Text = GenerateNewTextboxValue();
}
}
/// <summary>
/// Calculates a new value for the control
/// based on the configured expression.
/// </summary>
/// <returns></returns>
private string GenerateNewTextboxValue()
{
if(workItem == null) {
return "(null work item)";
}
if(expressionObject == null) {
return "(Expression not defined)";
}
object result = null;
try {
result = expressionObject.Evaluate();
}
catch(Exception ex) {
return string.Format("(Error in expression: {0})", ex.Message);
}
if(result == null) {
return "(Expression result is null)";
}
if(!IsNumericType(result.GetType())) {
return string.Format("(Expression result type is '{0}', which is not numeric)", result.GetType().Name);
}
try {
calculationValue = (decimal)result;
return calculationValue.ToString();
}
catch(Exception ex) {
return string.Format("(Error when converting result to number: {0})", ex.Message);
}
}
/// <summary>
/// This is invoked when the expression evaluator finds
/// a non-numeric item in the expression.
/// We assume it is a field name and retrieve its value
/// from the work item.
/// </summary>
/// <param name="name"></param>
/// <param name="args"></param>
private void expression_EvaluateParameter(string name, ParameterArgs args)
{
var field = workItemFields.Where(f => f.Name == name).SingleOrDefault();
if(field == null) {
throw new InvalidOperationException(string.Format("Can't find field named '{0}'", name));
}
if(!IsNumericType(field.FieldDefinition.SystemType)) {
throw new InvalidOperationException(string.Format("Field named '{0}' is not numeric", name));
}
if(!fieldsInExpression.Contains(field.Name)) {
fieldsInExpression.Add(field.Name);
}
args.Result = workItem.Fields[name].Value ?? 0;
}
private bool IsNumericType(Type type)
{
return (type.IsPrimitive && type != typeof(bool) && type != typeof(char)) || type == typeof(decimal);
}
#endregion
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
Under the secret identity of a C# programmer, a freaky guy who loves MSX computers and japanese culture is hidden. Beware!