C#.NET 自定计算公式,生成字符串并解析计算
目的:
可由用户自定计算公式,具体使用背景就不说了,看懂的话可以借鉴。
先看看效果吧。


不会描述,直接上代码吧
/// <summary>
/// 标记属性为可进行计算
/// </summary>
public class ComputerAttribute: Attribute
{
}
public class Cale
{
[Description("主键")]
public int Id { get; set; }
[Description("姓名")]
public string Name { get; set; }
[Description("重量")]
[Computer]
public decimal Weight { get; set; }
[Description("税率")]
[Computer]
public decimal Rage { get; set; }
[Description("人工费")]
[Computer]
public decimal RenGong { get; set; }
[Description("单价")]
[Computer]
public decimal Price { get; set; }
[Description("数量")]
[Computer]
public int Qty { get; set; }
}
public class SomeHelper
{
/// <summary>
/// 计算返回值
/// </summary>
/// <param name="expression">公式字符串</param>
/// <returns></returns>
public static float CalcByJs(string expression)
{
Microsoft.JScript.Vsa.VsaEngine ve = Microsoft.JScript.Vsa.VsaEngine.CreateEngine();
object returnValue = Microsoft.JScript.Eval.JScriptEvaluate((object)expression, ve);
return float.Parse(returnValue.ToString());
}
public static string CalcByTable(string exp)
{
DataTable dt = new DataTable();
return dt.Compute(exp, "false").ToString();
}
}
UI用了一些DevExpress控件
WebForm后台代码:
/// <summary>
/// 进行公式组装的对象
/// </summary>
public object CaleObject { get; set; }
/// <summary>
/// 获取传入对象的属性,有ComputerAttribute表示可以用于计算
/// </summary>
/// <returns></returns>
public List<BindingKV> GetDataSrouce()
{
List<BindingKV> data = new List<BindingKV>();
var properties= CaleObject.GetType().GetProperties();
foreach (PropertyInfo item in properties)
{
var cptAttr= item.GetCustomAttributes().FirstOrDefault(a=>a.GetType()==typeof(ComputerAttribute));
if (cptAttr != null)
{
object[] attr = item.GetCustomAttributes(typeof(DescriptionAttribute), false);
//描述特性
string description = attr.Length == 0 ? item.Name : ((DescriptionAttribute)attr[0]).Description;
data.Add(new BindingKV { EditValue=item.Name,TextValue=description });
}
}
return data;
}
模拟数据用于测试:
/// <summary>
/// 构造方法
/// </summary>
public CalculateExpression()
{
InitializeComponent();
CaleObject = new Cale {
Id=4, Name="Jack",Price=5.6m, Qty=343, Rage=0.17m, RenGong=200, Weight=50
};
}
listbox绑定数据源
private void CalculateExpression_Load(object sender, EventArgs e)
{
this.listBox1.ValueMember = "EditValue";
this.listBox1.DisplayMember = "TextValue";
this.listBox1.DataSource= GetDataSrouce();
}
差点忘了这2个:
public enum FiledType
{
/// <summary>
/// 字段
/// </summary>
Filed,
/// <summary>
/// 运算符
/// </summary>
Flag,
/// <summary>
/// 常量
/// </summary>
Constant
}
/// <summary>
/// ListBox Item绑定的类
/// </summary>
public class BindingKV
{
/// <summary>
/// ValueMember绑定的属性
/// </summary>
public string EditValue { get; set; }
/// <summary>
/// DisplayMember绑定的属性
/// </summary>
public string TextValue { get; set; }
}
添加公式按钮CheckButton到panel里,listbox是双击选中的添加,常量的话就是ButtonEdit的button ,Kind=Plus,其他运算符就是各个按钮啦;
/// <summary>
/// 加号,其他运算符一样的,改为相应的符号就行了
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
CommonAddCtl("+", "+", FiledType.Flag);
}
/// <summary>
/// 添加常量,ButtonEdit Mask设置Numeric ,n2
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void textEdit1_ButtonClick(object sender, DevExpress.XtraEditors.Controls.ButtonPressedEventArgs e)
{
if (e.Button.Kind == DevExpress.XtraEditors.Controls.ButtonPredefines.Plus)
{
if (!string.IsNullOrWhiteSpace(textEdit1.Text))
{
CommonAddCtl(textEdit1.Text, textEdit1.Text, FiledType.Constant);
}
}
}
/// <summary>
/// 字段双击添加
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void listBox1_DoubleClick(object sender, EventArgs e)
{
if (this.listBox1.SelectedIndex >= 0)
{
BindingKV selected = (BindingKV)this.listBox1.SelectedItem;
CommonAddCtl(selected.TextValue, selected.EditValue, FiledType.Filed);
}
}
/// <summary>
/// 统一添加控件的方法
/// </summary>
/// <param name="text"> CheckButton控件显示文本</param>
/// <param name="tag">CheckButton的Tag属性,存放属性名</param>
/// <param name="type"></param>
public void CommonAddCtl(string text, string tag, FiledType type)
{
Point p = GetPoint(this.CtlPanel);
CheckButton btn = new CheckButton
{
Text = text,
ButtonStyle = DevExpress.XtraEditors.Controls.BorderStyles.NoBorder,
AutoSize = true,
};
if (type == FiledType.Filed)
{
btn.Font = new Font("", 11f, FontStyle.Underline);
btn.Tag = "{" + tag +"}";
}
else
{
btn.Font = new Font("", 11f, FontStyle.Regular);
btn.Tag = tag ;
}
btn.Location = p;
btn.CheckedChanged += Btn_CheckedChanged;
CtlPanel.Controls.Add(btn);
}
/// <summary>
/// 只能选中一个
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Btn_CheckedChanged(object sender, EventArgs e)
{
CheckButton btn = ((CheckButton)sender);
if (btn.Checked)
{
foreach (Control item in CtlPanel.Controls)
{
if (item is CheckButton)
{
if (item.GetHashCode() != btn.GetHashCode())
{
((CheckButton)item).Checked = false;
}
}
}
}
}
删除、全删panel里的CheckButton,删除要选中上面要删除的CheckButton
/// <summary>
/// 删除
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button9_Click(object sender, EventArgs e)
{
foreach (Control item in CtlPanel.Controls)
{
if (item is CheckButton)
{
if (((CheckButton)item).Checked)
{
CtlPanel.Controls.Remove(item);
}
}
}
}
/// <summary>
/// 全删
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button10_Click(object sender, EventArgs e)
{
CtlPanel.Controls.Clear();
}
panel里添加、删除CheckButton,更新对于label的公式,这里的公式是我们需要的,上面那个公式给用户用(看)的,晕~~~
/// <summary>
/// panel里添加控件处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CtlPanel_ControlAdded(object sender, ControlEventArgs e)
{
ChangeExpression();
}
/// <summary>
/// 获取所有CheckButton,拼接Tag属性,更新到label中
/// </summary>
private void ChangeExpression()
{
string expression = string.Empty;
if (this.CtlPanel.Controls.Count < 1)
{
this.label1.Text = string.Empty;
}else
{
foreach (var item in this.CtlPanel.Controls)
{
if(item is CheckButton)
{
expression+=((CheckButton)item).Tag.ToString();
}
}
this.label1.Text = expression;
}
}
/// <summary>
/// panel里删除控件处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void CtlPanel_ControlRemoved(object sender, ControlEventArgs e)
{
ChangeExpression();
}
最后,拿到公式,上面我们把{属性名称}加到公式中了,现在就通过正则匹配,获取对于属性的值,把数值替换到公式里,然后调用上面的CalcByJs()方法进行计算;
/// <summary>
/// 计算
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button11_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(this.label1.Text))
{
MessageBox.Show("没有添加公式哦");
return;
}
//获取公式
string gongShi = this.label1.Text;
string pi = @"{[^+-/*%]+}";
Regex reg = new Regex(pi);
var get = reg.Matches(gongShi);
//通过匹配公式,获取参与计算的字段
List<string> fileds = new List<string>();
foreach (Match item in get)
{
fileds.Add(item.Value);
}
#region 通过反射,获取对象属性值,替换成可进行计算的数值公式
var properties = CaleObject.GetType().GetProperties();
foreach (string filed in fileds)
{
var prop = properties.FirstOrDefault(a => "{" + a.Name + "}" == filed);
string value = prop.GetValue(CaleObject).ToString();
gongShi = gongShi.Replace(filed, value);
}
#endregion
//执行计算
try
{
this.label1.Text = gongShi;
//float result= SomeHelper.CalcByJs(gongShi);
//if (result.Equals(float.PositiveInfinity))
//{
// MessageBox.Show("除数不能为零");
// return;
//}
//MessageBox.Show(result.ToString());
MessageBox.Show(SomeHelper.CalcByTable(gongShi));
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
最后总结:


浙公网安备 33010602011771号