資料庫可以定義表不同列之間的計算公式,進行自動公式計算,但如何實作行上的動态公式計算呢?行由于可以動态擴充,在某些應用場景下将能很好的解決實際問題。這也是Excel為什麼如何好用的一個重要原因,它可以建立單元格之間的勾稽關系,通過函數的方式實作強大的計算能力。
下面我們就探讨一下如何在WPF中實作一種基于行字段的動态公式計算,具體的示例如下所示。
1 項目結構
首先,在Visual Studio IDE中建立一個WPF應用程式WpfApp_DynCalc,并添加一個類DynCalc.cs,項目具體的結構如下圖所示:

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 效果
運作程式,其中值列的值為初始值,點選計算後會根據公式列配置進行動态計算,初始化界面如下:
可見現在的值是根據公式配置進行動态計算的。當然代碼經過擴充還可以支援函數和簡單的邏輯判斷,如if ...else等。實作更強大的邏輯處理。