数据库可以定义表不同列之间的计算公式,进行自动公式计算,但如何实现行上的动态公式计算呢?行由于可以动态扩展,在某些应用场景下将能很好的解决实际问题。这也是Excel为什么如何好用的一个重要原因,它可以建立单元格之间的勾稽关系,通过函数的方式实现强大的计算能力。
下面我们就探讨一下如何在WPF中实现一种基于行字段的动态公式计算,具体的示例如下所示。
1 项目结构
首先,在Visual Studio IDE中新建一个WPF应用程序WpfApp_DynCalc,并添加一个类DynCalc.cs,项目具体的结构如下图所示:
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 效果
运行程序,其中值列的值为初始值,点击计算后会根据公式列配置进行动态计算,初始化界面如下:
可见现在的值是根据公式配置进行动态计算的。当然代码经过扩展还可以支持函数和简单的逻辑判断,如if ...else等。实现更强大的逻辑处理。