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

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

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

1 项目结构


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

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


2 代码实现


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

<Window x: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>
        <Style TargetType="DataGrid">
            <!--网格线颜色-->
            <Setter Property="CanUserResizeColumns" Value="false"/>
            <Setter Property="Background" Value="#E6DBBB" />
            <Setter Property="BorderBrush" Value="#d6c79b" />
            <Setter Property="HorizontalGridLinesBrush">
                <Setter.Value>
                    <SolidColorBrush Color="#d6c79b"/>
                </Setter.Value>
            </Setter>
            <Setter Property="VerticalGridLinesBrush">
                <Setter.Value>
                    <SolidColorBrush Color="#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>-->

        <Style TargetType="DataGridColumnHeader">
            <Setter Property="SnapsToDevicePixels" Value="True" />
            <Setter Property="MinWidth" Value="0" />
            <Setter Property="MinHeight" Value="28" />
            <Setter Property="Foreground" Value="#323433" />
            <Setter Property="FontSize" Value="14" />
            <Setter Property="Cursor" Value="Hand" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="DataGridColumnHeader">
                        <Border x:Name="BackgroundBorder" BorderThickness="0,1,0,1"
                             BorderBrush="#e6dbba"
                              Width="Auto">
                            <Grid >
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <ContentPresenter  Margin="0,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                                <Path x: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" />
                                <Rectangle Width="1" Fill="#d6c79b" HorizontalAlignment="Right" Grid.ColumnSpan="1" />
                                <!--<TextBlock  Background="Red">
                            <ContentPresenter></ContentPresenter></TextBlock>-->
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Height" Value="25"/>
        </Style>
        <!--行样式触发-->
        <!--背景色改变必须先设置cellStyle 因为cellStyle会覆盖rowStyle样式-->
        <Style  TargetType="DataGridRow">
            <Setter Property="Background" Value="#F2F2F2" />
            <Setter Property="Height" Value="25"/>
            <Setter Property="Foreground" Value="Black" />
            <Style.Triggers>
                <!--隔行换色-->
                <Trigger Property="AlternationIndex" Value="0" >
                    <Setter Property="Background" Value="#e7e7e7" />
                </Trigger>
                <Trigger Property="AlternationIndex" Value="1" >
                    <Setter Property="Background" Value="#f2f2f2" />
                </Trigger>

                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="LightGray"/>
                    <!--<Setter Property="Foreground" Value="White"/>-->
                </Trigger>

                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Foreground" Value="Black"/>
                </Trigger>
            </Style.Triggers>
        </Style>

        <!--单元格样式触发-->
        <Style TargetType="DataGridCell">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="DataGridCell">
                        <TextBlock TextAlignment="Center" VerticalAlignment="Center"  >
                           <ContentPresenter />
                        </TextBlock>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <!--<Setter Property="Background" Value="White"/>
                <Setter Property="BorderThickness" Value="0"/>-->
                    <Setter Property="Foreground" Value="Black"/>
                </Trigger>
            </Style.Triggers>
        </Style>
        </Grid.Resources>


        <DataGrid  Name="dgrid"   HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="256" Width="498"  AutoGenerateColumns="False" >
            <DataGrid.Columns>
                <DataGridTextColumn  Header="指标" Binding="{Binding Zb}" Width="118"/>
                <DataGridTextColumn  Header="值" Binding="{Binding Value}" Width="100"/>
                <DataGridTextColumn  Header="公式" Binding="{Binding Formula}" Width="188"/>
            </DataGrid.Columns>
        </DataGrid>
        <Button Content="计 算" HorizontalAlignment="Left" Margin="419,281,0,0" VerticalAlignment="Top" Width="85" Click="Button_Click_1" Height="28" />
        <TextBlock Name="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,核心代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.Collections.ObjectModel;
namespace WpfApp_DynCalc
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();


            ObservableCollection<Dynformula> ofs = new ObservableCollection<Dynformula>();
            ofs.Add(new Dynformula { Zb = "A", Value = "1", Formula = "" });
            ofs.Add(new Dynformula { Zb = "B", Value = "2", Formula = "2*A+1" });
            ofs.Add(new Dynformula { Zb = "C", Value = "3", Formula = "B*B" });
            ofs.Add(new Dynformula { Zb = "D", Value = "4", Formula = "C-2" });
            ofs.Add(new Dynformula { Zb = "Z", Value = "5", Formula = "D+C" });
            this.dgrid.ItemsSource = ofs;


        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            this.lblResult.Text = "计算...";
             this.dgrid.ItemsSource=   DynCalc.CalcScript(ref this.dgrid);
             this.lblResult.Text = "计算完成!";

        }
    }

    public class Dynformula
    {
        private string zb;

        public string Zb
        {
            get { return zb; }
            set { zb = value; }
        }
        private string value;

        public string Value
        {
            get { return this.value; }
            set { this.value = value; }
        }
        private string formula;

        public string Formula
        {
            get { return formula; }
            set { formula = value; }
        }

    }
}

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp_DynCalc
{
   using System.Collections.ObjectModel;
   using System.Windows;
   using System.Windows.Controls;
   using System.Reflection;
   using System.Globalization;
   using Microsoft.CSharp;
   using System.CodeDom;
   using System.CodeDom.Compiler;
   public static class DynCalc
    {

       public static ObservableCollection<Dynformula> CalcScript(ref DataGrid dgrid)
       {

           CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider();
           ICodeCompiler objICodeCompiler = objCSharpCodePrivoder.CreateCompiler();
           CompilerParameters objCompilerParameters = new CompilerParameters();
           objCompilerParameters.ReferencedAssemblies.Add("System.dll");
           objCompilerParameters.GenerateExecutable = false;
           objCompilerParameters.GenerateInMemory = true;
           CompilerResults cr = objICodeCompiler.CompileAssemblyFromSource(objCompilerParameters, GenerateCode(ref dgrid));
           if (cr.Errors.HasErrors)
           {
               Console.WriteLine("编译错误:");
               foreach (CompilerError err in cr.Errors)
               {
                   Console.WriteLine(err.ErrorText);
               }
               return null;
           }
           else
           {
               // 通过反射,调用实例
               Assembly objAssembly = cr.CompiledAssembly;
               object objDynCalc = objAssembly.CreateInstance("DynamicCodeGenerate.RunScript");
               //MethodInfo objMI = objHelloWorld.GetType().GetMethod("OutPut");
               //Console.WriteLine(objMI.Invoke(objHelloWorld, null));

               ObservableCollection<Dynformula> ofsnew = new ObservableCollection<Dynformula>();
               //循环datagrid进行公式计算并赋值
               for (int i = 0; i < dgrid.Items.Count; i++)
               {
                   Dynformula item = dgrid.Items[i] as Dynformula;
                   if (item == null)
                   {
                       break;
                   }
                   string zb = item.Zb;
                   PropertyInfo pinfo = objDynCalc.GetType().GetProperty(zb);
                   if (pinfo != null && pinfo.CanRead)                   {
                       //获取属性get值
                       object obj_Name = pinfo.GetValue(objDynCalc, null);
                     //  item.Value = obj_Name.ToString();
                       ofsnew.Add(new Dynformula { Zb = item.Zb, Value = obj_Name.ToString(), Formula = item.Formula});
                   }
               }
               return ofsnew;
           }
       }
       /// <summary>
       /// 计算逻辑C#脚本动态构建
       /// </summary>
       /// <param name="dgrid">存有指标以及指标计算公式的datagrid</param>
       /// <returns>C#脚本</returns>
        static string GenerateCode(ref DataGrid dgrid)
        {
            StringBuilder sb = new StringBuilder();
            StringBuilder sb构建函数内容 = new StringBuilder();
            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(int i=0;i<dgrid.Items.Count;i++)
            {
                Dynformula item = dgrid.Items[i] as Dynformula;
                if (item == null)
                {
                    break;
                }
                string zb = 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("}");

            string code = sb.ToString();
            Console.WriteLine(code);
            return code;
        }

    }
}

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

3 效果


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

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

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

上一篇:Quartz.NET开源作业调度框架系列(五):AdoJobStore保存job到数据库


下一篇:Ansible playbook忽略错误继续执行