WPF实现强大的动态公式计算

简介: 数据库可以定义表不同列之间的计算公式,进行自动公式计算,但如何实现行上的动态公式计算呢?行由于可以动态扩展,在某些应用场景下将能很好的解决实际问题。本文就探讨一下如何在WPF中实现一种基于行字段的动态公式计算。

     数据库可以定义表不同列之间的计算公式,进行自动公式计算,但如何实现行上的动态公式计算呢?行由于可以动态扩展,在某些应用场景下将能很好的解决实际问题。这也是Excel为什么如何好用的一个重要原因,它可以建立单元格之间的勾稽关系,通过函数的方式实现强大的计算能力。

    下面我们就探讨一下如何在WPF中实现一种基于行字段的动态公式计算,具体的示例如下所示。

1 项目结构


    首先,在Visual Studio IDE中新建一个WPF应用程序WpfApp_DynCalc,并添加一个类DynCalc.cs,项目具体的结构如下图所示:

1.png


2 代码实现


  在WPF中,UI界面是通过XAML语言实现的,主界面对应的文件为MainWindow.xaml,因此在Visual Studio中对文件MainWindow.xaml进行编辑,核心代码如下:

<Windowx:Class="WpfApp_DynCalc.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="WPF动态计算示例"Height="350"Width="525"><Grid><Grid.Resources><StyleTargetType="DataGrid"><!--网格线颜色--><SetterProperty="CanUserResizeColumns"Value="false"/><SetterProperty="Background"Value="#E6DBBB"/><SetterProperty="BorderBrush"Value="#d6c79b"/><SetterProperty="HorizontalGridLinesBrush"><Setter.Value><SolidColorBrushColor="#d6c79b"/></Setter.Value></Setter><SetterProperty="VerticalGridLinesBrush"><Setter.Value><SolidColorBrushColor="#d6c79b"/></Setter.Value></Setter></Style><!--标题栏样式--><!--<Style  TargetType="DataGridColumnHeader" ><Setter Property="Width" Value="50"/><Setter Property="Height" Value="30"/><Setter Property="FontSize" Value="14" /><Setter Property="Background" Value="White" /><Setter  Property="FontWeight"  Value="Bold"/></Style>--><StyleTargetType="DataGridColumnHeader"><SetterProperty="SnapsToDevicePixels"Value="True"/><SetterProperty="MinWidth"Value="0"/><SetterProperty="MinHeight"Value="28"/><SetterProperty="Foreground"Value="#323433"/><SetterProperty="FontSize"Value="14"/><SetterProperty="Cursor"Value="Hand"/><SetterProperty="Template"><Setter.Value><ControlTemplateTargetType="DataGridColumnHeader"><Borderx:Name="BackgroundBorder"BorderThickness="0,1,0,1"BorderBrush="#e6dbba"Width="Auto"><Grid><Grid.ColumnDefinitions><ColumnDefinitionWidth="*"/></Grid.ColumnDefinitions><ContentPresenterMargin="0,0,0,0"VerticalAlignment="Center"HorizontalAlignment="Center"/><Pathx:Name="SortArrow"Visibility="Collapsed"Data="M0,0 L1,0 0.5,1 z"Stretch="Fill"Grid.Column="2"Width="8"Height="6"Fill="White"Margin="0,0,50,0"VerticalAlignment="Center"RenderTransformOrigin="1,1"/><RectangleWidth="1"Fill="#d6c79b"HorizontalAlignment="Right"Grid.ColumnSpan="1"/><!--<TextBlock  Background="Red"><ContentPresenter></ContentPresenter></TextBlock>--></Grid></Border></ControlTemplate></Setter.Value></Setter><SetterProperty="Height"Value="25"/></Style><!--行样式触发--><!--背景色改变必须先设置cellStyle 因为cellStyle会覆盖rowStyle样式--><StyleTargetType="DataGridRow"><SetterProperty="Background"Value="#F2F2F2"/><SetterProperty="Height"Value="25"/><SetterProperty="Foreground"Value="Black"/><Style.Triggers><!--隔行换色--><TriggerProperty="AlternationIndex"Value="0"><SetterProperty="Background"Value="#e7e7e7"/></Trigger><TriggerProperty="AlternationIndex"Value="1"><SetterProperty="Background"Value="#f2f2f2"/></Trigger><TriggerProperty="IsMouseOver"Value="True"><SetterProperty="Background"Value="LightGray"/><!--<Setter Property="Foreground" Value="White"/>--></Trigger><TriggerProperty="IsSelected"Value="True"><SetterProperty="Foreground"Value="Black"/></Trigger></Style.Triggers></Style><!--单元格样式触发--><StyleTargetType="DataGridCell"><SetterProperty="Template"><Setter.Value><ControlTemplateTargetType="DataGridCell"><TextBlockTextAlignment="Center"VerticalAlignment="Center"><ContentPresenter/></TextBlock></ControlTemplate></Setter.Value></Setter><Style.Triggers><TriggerProperty="IsSelected"Value="True"><!--<Setter Property="Background" Value="White"/><Setter Property="BorderThickness" Value="0"/>--><SetterProperty="Foreground"Value="Black"/></Trigger></Style.Triggers></Style></Grid.Resources><DataGridName="dgrid"HorizontalAlignment="Left"Margin="10,10,0,0"VerticalAlignment="Top"Height="256"Width="498"AutoGenerateColumns="False"><DataGrid.Columns><DataGridTextColumnHeader="指标"Binding="{Binding Zb}"Width="118"/><DataGridTextColumnHeader="值"Binding="{Binding Value}"Width="100"/><DataGridTextColumnHeader="公式"Binding="{Binding Formula}"Width="188"/></DataGrid.Columns></DataGrid><ButtonContent="计 算"HorizontalAlignment="Left"Margin="419,281,0,0"VerticalAlignment="Top"Width="85"Click="Button_Click_1"Height="28"/><TextBlockName="lblResult"HorizontalAlignment="Left"Margin="28,281,0,0"TextWrapping="Wrap"Text=""VerticalAlignment="Top"/></Grid></Window>

从代码中可知,界面中通过DataGrid控件生成一个表格,其中绑定了相关的变量,进行动态的数据展现。而计算但你的后台逻辑是通过Button_Click_1事件实现的,后台代码文件为MainWindow.xaml.cs,编辑后台文件MainWindow.xaml.cs,核心代码如下:

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Threading.Tasks;
usingSystem.Windows;
usingSystem.Windows.Controls;
usingSystem.Windows.Data;
usingSystem.Windows.Documents;
usingSystem.Windows.Input;
usingSystem.Windows.Media;
usingSystem.Windows.Media.Imaging;
usingSystem.Windows.Navigation;
usingSystem.Windows.Shapes;
usingSystem.Collections.ObjectModel;
namespaceWpfApp_DynCalc{
/// <summary>/// MainWindow.xaml 的交互逻辑/// </summary>publicpartialclassMainWindow : Window    {
publicMainWindow()
        {
InitializeComponent();
ObservableCollection<Dynformula>ofs=newObservableCollection<Dynformula>();
ofs.Add(newDynformula { Zb="A", Value="1", Formula="" });
ofs.Add(newDynformula { Zb="B", Value="2", Formula="2*A+1" });
ofs.Add(newDynformula { Zb="C", Value="3", Formula="B*B" });
ofs.Add(newDynformula { Zb="D", Value="4", Formula="C-2" });
ofs.Add(newDynformula { Zb="Z", Value="5", Formula="D+C" });
this.dgrid.ItemsSource=ofs;
        }
privatevoidButton_Click_1(objectsender, RoutedEventArgse)
        {
this.lblResult.Text="计算...";
this.dgrid.ItemsSource=DynCalc.CalcScript(refthis.dgrid);
this.lblResult.Text="计算完成!";
        }
    }
publicclassDynformula    {
privatestringzb;
publicstringZb        {
get { returnzb; }
set { zb=value; }
        }
privatestringvalue;
publicstringValue        {
get { returnthis.value; }
set { this.value=value; }
        }
privatestringformula;
publicstringFormula        {
get { returnformula; }
set { formula=value; }
        }
    }
}

其中计算按钮的逻辑中,调用了一个核心方法DynCalc.CalcScript(ref this.dgrid),它在类文件DynCalc.cs中定义,其核心带代码如下所示:

usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Threading.Tasks;
namespaceWpfApp_DynCalc{
usingSystem.Collections.ObjectModel;
usingSystem.Windows;
usingSystem.Windows.Controls;
usingSystem.Reflection;
usingSystem.Globalization;
usingMicrosoft.CSharp;
usingSystem.CodeDom;
usingSystem.CodeDom.Compiler;
publicstaticclassDynCalc    {
publicstaticObservableCollection<Dynformula>CalcScript(refDataGriddgrid)
       {
CSharpCodeProviderobjCSharpCodePrivoder=newCSharpCodeProvider();
ICodeCompilerobjICodeCompiler=objCSharpCodePrivoder.CreateCompiler();
CompilerParametersobjCompilerParameters=newCompilerParameters();
objCompilerParameters.ReferencedAssemblies.Add("System.dll");
objCompilerParameters.GenerateExecutable=false;
objCompilerParameters.GenerateInMemory=true;
CompilerResultscr=objICodeCompiler.CompileAssemblyFromSource(objCompilerParameters, GenerateCode(refdgrid));
if (cr.Errors.HasErrors)
           {
Console.WriteLine("编译错误:");
foreach (CompilerErrorerrincr.Errors)
               {
Console.WriteLine(err.ErrorText);
               }
returnnull;
           }
else           {
// 通过反射,调用实例AssemblyobjAssembly=cr.CompiledAssembly;
objectobjDynCalc=objAssembly.CreateInstance("DynamicCodeGenerate.RunScript");
//MethodInfo objMI = objHelloWorld.GetType().GetMethod("OutPut");//Console.WriteLine(objMI.Invoke(objHelloWorld, null));ObservableCollection<Dynformula>ofsnew=newObservableCollection<Dynformula>();
//循环datagrid进行公式计算并赋值for (inti=0; i<dgrid.Items.Count; i++)
               {
Dynformulaitem=dgrid.Items[i] asDynformula;
if (item==null)
                   {
break;
                   }
stringzb=item.Zb;
PropertyInfopinfo=objDynCalc.GetType().GetProperty(zb);
if (pinfo!=null&&pinfo.CanRead)                   {
//获取属性get值objectobj_Name=pinfo.GetValue(objDynCalc, null);
//  item.Value = obj_Name.ToString();ofsnew.Add(newDynformula { Zb=item.Zb, Value=obj_Name.ToString(), Formula=item.Formula});
                   }
               }
returnofsnew;
           }
       }
/// <summary>/// 计算逻辑C#脚本动态构建/// </summary>/// <param name="dgrid">存有指标以及指标计算公式的datagrid</param>/// <returns>C#脚本</returns>staticstringGenerateCode(refDataGriddgrid)
        {
StringBuildersb=newStringBuilder();
StringBuildersb构建函数内容=newStringBuilder();
sb.Append("using System;");
sb.Append(Environment.NewLine);
sb.Append("namespace DynamicCodeGenerate");
sb.Append(Environment.NewLine);
sb.Append("{");
sb.Append(Environment.NewLine);
sb.Append("    public class RunScript");
sb.Append(Environment.NewLine);
sb.Append("    {");
//------------------------------------------------------------for(inti=0;i<dgrid.Items.Count;i++)
            {
Dynformulaitem=dgrid.Items[i] asDynformula;
if (item==null)
                {
break;
                }
stringzb=item.Zb;
sb.Append(Environment.NewLine);
sb.AppendFormat("        public double _{0};", item.Zb);
sb.Append(Environment.NewLine);
sb.AppendFormat("        public double {0}",item.Zb);
sb.Append(Environment.NewLine);
sb.Append("        {");
sb.Append(Environment.NewLine);
if (item.Formula.Trim() !="")
                {
sb.Append("             set{ "+item.Zb+"=value;}" );
sb.Append(Environment.NewLine);
sb.Append("             get{return "+item.Formula+";}");
                }
else                {
sb.Append("             set{ _"+item.Zb+"=value;}");
sb.Append(Environment.NewLine);
sb.Append("             get{return  _"+item.Zb+";} ");
sb.Append(Environment.NewLine);
sb构建函数内容.Append("       _"+item.Zb+"="+item.Value);
                }
sb.Append(Environment.NewLine);
sb.Append("        }");
sb.Append(Environment.NewLine);
            }
//--------------------------------------------//构造函数进行赋值sb.Append(Environment.NewLine);
sb.Append("        public  RunScript()");
sb.Append(Environment.NewLine);
sb.Append("        {");
sb.Append(Environment.NewLine);
sb.AppendFormat("            {0};",sb构建函数内容.ToString());
sb.Append(Environment.NewLine);
sb.Append("        }");
sb.Append(Environment.NewLine);
//----------------------------------------------sb.Append(Environment.NewLine);
sb.Append("        public string OutPut()");
sb.Append(Environment.NewLine);
sb.Append("        {");
sb.Append(Environment.NewLine);
sb.Append("             return \"Hello world!\";");
sb.Append(Environment.NewLine);
sb.Append("        }");
//-----------------------------------------sb.Append(Environment.NewLine);
sb.Append("    }");
sb.Append(Environment.NewLine);
sb.Append("}");
stringcode=sb.ToString();
Console.WriteLine(code);
returncode;
        }
    }
}

其中用到了C#内置的CSharpCodeProvider类,它内置了一个C#语言的编译器,可以用来动态的执行C#代码,因此,单击按钮后,就需要在表格中获取指标的值,并动态构建脚本来执行,这样就可以获取到对应的变量值,并显示到UI上。

3 效果


运行程序,其中值列的值为初始值,点击计算后会根据公式列配置进行动态计算,初始化界面如下:

2.png

可见现在的值是根据公式配置进行动态计算的。当然代码经过扩展还可以支持函数和简单的逻辑判断,如if ...else等。实现更强大的逻辑处理。

相关文章
|
C#
WPF 界面实现多语言支持 中英文切换 动态加载资源字典
原文:WPF 界面实现多语言支持 中英文切换 动态加载资源字典 1、使用资源字典,首先新建两个字典文件en-us.xaml、zh-cn.xaml。定义中英文的字符串在这里面【注意:添加xmlns:s="clr-namespace:System;assembly=mscorlib】 zh-cn.
3188 0
|
C# 数据格式 XML
WPF 资源(StaticResource 静态资源、DynamicResource 动态资源、添加二进制资源、绑定资源树)
原文:WPF 资源(StaticResource 静态资源、DynamicResource 动态资源、添加二进制资源、绑定资源树) 一、WPF对象级(Window对象)资源的定义与查找 实例一: StaticR...
8431 0
|
移动开发 开发框架 网络协议
WPF+ASP.NET SignalR实现动态折线图
WPF+ASP.NET SignalR实现动态折线图
137 0
|
IDE C# 开发工具
WPF钟表效果实现
中WPF中的RotateTransform实现UI元素的旋转,并模拟钟表的秒针、分针和时针。
1196 0
WPF钟表效果实现
|
网络协议 C# 移动开发
C# WPF上位机实现和下位机TCP通讯
C# WPF上位机实现和下位机TCP通讯下位机使用北京大华程控电源DH1766-1,上位机使用WPF。实现了电压电流实时采集,曲线显示。上午在公司调试成功,手头没有程控电源,使用TCP服务端模拟。昨天写的TCP服务端正好排上用场。
2400 0
|
C# 存储
WPF 动态模拟CPU 使用率曲线图
原文:WPF 动态模拟CPU 使用率曲线图      在工作中经常会遇到需要将一组数据绘制成曲线图的情况,最简单的方法是将数据导入Excel,然后使用绘图功能手动生成曲线图。但是如果基础数据频繁更改,则手动创建图形可能会变得枯燥乏味。
1069 0
|
C#
在WPF中快速实现键盘钩子
原文:在WPF中快速实现键盘钩子 大部分的时候,当我们需要键盘事件的时候,可以通过在主窗口注册KeyBinding来实现,不过,有的时候我们需要的是全局键盘事件,想在任何一个地方都能使用,最开始的时候我是通过键盘钩子来实现的, 不过键盘钩子这种DLL调用的方式怎么都看着不大爽,这里介绍一种通过EventManager快速实现键盘事件感知的例子。
1085 0
|
C#
WPF中获取TreeView以及ListView获取其本身滚动条的方法,可实现自行调节scoll滚动的位置(可相应获取任何控件中的内部滚动条)
原文:WPF中获取TreeView以及ListView获取其本身滚动条的方法,可实现自行调节scoll滚动的位置(可相应获取任何控件中的内部滚动条) 对于TreeView而言: TreeViewAutomationPeer lvap = new TreeViewAuto...
2399 0
|
C#
WPF 动态更换样式文件
原文:WPF 动态更换样式文件   ApplySkinFromMenuItem("Style/BigImgStyle.xaml", "Style/FileListStyle.xaml");     //换肤        void ApplySkinFromMenuItem(string sou...
963 0
|
前端开发 C# 算法
WPF路径动画(动态逆向动画)
原文:WPF路径动画(动态逆向动画) WPF 中的Path.Data 不再多介绍,M开始坐标点 C弧度坐标点 L 直线坐标点   个人写了关于Path.Data数据反向,意思就是把Path的数据逆转,但是图形是没有变化的 Xaml代码如下: ...
1985 0