WinForms版本OLAP快速入门 > 构建定制用户界面 |
上个章节中所有的例子都使用了C1OlapPage 控件,控件包含了完整的UI因此只需要很少甚至不需要编写代码。在本章节中,我们将尝试不使用C1OlapPage创建一个OLAP应用。这将创建一个完整的定制UI,通过使用C1OlapGrid, C1OlapChart, 和其他标准的 .NET控件实现。
应用完整的源代码可以从”CustomUI”示例中查看。
下图显示应用的设计视图:
在表单的上部有一个面板显示应用标题。在表单右侧安置了一个垂直的工具栏控件,其中包含三组按钮。最上面的分组允许用户选择三种预定义视图中的一种,销售人员的销售额,产品或是国家。下一个分组允许用户在产品价格上应用一个数据库过滤器(昂贵,中等或是廉价)。最后一个按钮提供报表。
报表剩下的区域被一个左侧显示C1OlapGrid 以及右侧显示C1OlapChart 的脚本容器填充。控件将显示当前选中的视图。
表单还包含一个C1OlapPrintDocument 组件,主要用于生成报表。该组件在图片上是不可见的,因为它仅在表单下层的托盘区域显示。C1OlapPrintDocument 组件通过OlapGrid 和OlapChart属性连接到页面上的OLAP控件,这些属性将在设计阶段完成设置。
最后,表单上还有一个C1OlapPanel 控件。它的Visible属性设置为false,因此用户将不会看到它。这个不可见的控件主要用于作为表格和图表的数据源,它将负责数据过滤以及汇总数据。表格和图表都含有它们自己C1OlapPanel 中的DataSource属性。
一旦所有的控件就位,让我们添加代码,连接它们然后让应用开始工作。
首先,让我们获取数据,并将其赋值给C1OlapPanel:
private void Form1_Load(object sender, EventArgs e)
{
// load data
var da = new OleDbDataAdapter("select * from Invoices",
GetConnectionString());
var dt = new DataTable();
da.Fill(dt);
// assign it to C1OlapPanel that is driving the app
this.c1OlapPanel1.DataSource = dt;
// start with the SalesPerson view, all products
_btnSalesperson.PerformClick();
_btnAllPrices.PerformClick();
}
这些代码使用DataAdapter从NorthWind数据库中获取数据,然后将结果DataTable赋值给C1OlapPanel.DataSource属性。然后使用PerformClick方法来模拟单击两个按钮,初始化当前视图和过滤器.
选择当前视图按钮事件句柄的代码如下所示:
void _btnSalesperson_Click(object sender, EventArgs e)
{
CheckButton(sender);
BuildView("Salesperson");
}
void _btnProduct_Click(object sender, EventArgs e)
{
CheckButton(sender);
BuildView("ProductName");
}
void _btnCountry_Click(object sender, EventArgs e)
{
CheckButton(sender);
BuildView("Country");
}
所有的句柄都使用BuildView助手方法,该方法如下所示:
// rebuild the view after a button was clicked
void BuildView(string fieldName)
{
// get olap engine
var olap = c1OlapPanel1.OlapEngine;
// stop updating until done
olap.BeginUpdate();
// format order dates to group by year
var f = olap.Fields["OrderDate"];
f.Format = "yyyy";
// clear all fields
olap.RowFields.Clear();
olap.ColumnFields.Clear();
olap.ValueFields.Clear();
// build up view
olap.ColumnFields.Add("OrderDate");
olap.RowFields.Add(fieldName);
olap.ValueFields.Add("ExtendedPrice");
// restore updates
olap.EndUpdate();
}
BuildView方法获取一个C1OlapPanel 对象提供的C1OlapEngine对象,并且立即调用BeginUpdate方法停止更新直到完成了新的视图定义。这样做能提高性能。
代码设置”OrderDate”字段的格式为”yyyy”,因此销售额将通过年份来进行分组。清除掉引擎中的RowFields, ColumnFields, 和 ValueFields集合来重新生成视图,然后添加想要显示的字段。调用者传入的”fieldName”参数仅包含一个字段的名称,并且本例中该参数将在视图中被改动。
当所有这些都完成之后,代码调用EndUpdate方法,C1OlapPanel将更新输出表。
在运行程序之前,让我们看一下实现过滤的代码。事件句柄如下所示:
void _btnExpensive_Click(object sender, EventArgs e)
{
CheckButton(sender);
SetPriceFilter("Expensive Products (price > $50)", 50, double.MaxValue);
}
void _btnModerate_Click(object sender, EventArgs e)
{
CheckButton(sender);
SetPriceFilter("Moderately Priced Products ($20 < price < $50)", 20, 50);
}
void _btnInexpensive_Click(object sender, EventArgs e)
{
CheckButton(sender);
SetPriceFilter("Inexpensive Products (price < $20)", 0, 20);
}
void _btnAllProducts_Click(object sender, EventArgs e)
{
CheckButton(sender);
SetPriceFilter("All Products", 0, double.MaxValue);
}
所有的句柄都使用SetPriceFilter助手方法,该方法如下所示:
// apply a filter to the product price
void SetPriceFilter(string footerText, double min, double max)
{
// get olap engine
var olap = c1OlapPanel1.OlapEngine;
// stop updating until done
olap.BeginUpdate();
// make sure unit price field is active in the view
var field = olap.Fields["UnitPrice"];
olap.FilterFields.Add(field);
// customize the filter to apply the condition
var filter = field.Filter;
filter.Clear();
filter.Condition1.Operator =
C1.Olap.ConditionOperator.GreaterThanOrEqualTo;
filter.Condition1.Parameter = min;
filter.Condition2.Operator =
C1.Olap.ConditionOperator.LessThanOrEqualTo;
filter.Condition2.Parameter = max;
// restore updates
olap.EndUpdate();
// set report footer
c1OlapPrintDocument1.FooterText = footerText;
}
和之前类似,代码获取到C1OlapEngine的引用,然后立即调用BeginUpdate方法。
随后,它获取到”UnitPrice”字段的引用,这将用于过滤数据。”UnitPrice”字段将添加到引擎的FilterFields集合中,因此过滤器可以在当前视图中应用。
这是一个非常重要的细节。如果一个字段不存在与任何视图集合(RowFields, ValueFields, FilterFields)中,它就不能包含在视图中,而且它的过滤器属性无法通过任何方式影响视图。
代码接下来将通过设置两个视图中包含值的取值范围,从而对”UnitPrice”字段的过滤器属性进行设置。该范围通过”min”和”max”两个参数来定义。除了使用条件,你还能以列表形式提供将要包含在视图中的数值来实现这一效果。添加条件通常更合适处理数字值和列表,而不是字符串数值和枚举类型。
最后,代码调用EndUpdate方法,然后设置C1OlapPrintDocument的FooterText属性,之后它将可以自动的显示在任何报表中。
上述方法使用了另一个叫做CheckButton助手方法,方法如下所示:
// show which button was pressed
void CheckButton(object pressedButton)
{
var btn = pressedButton as ToolStripButton;
btn.Checked = true;
var items = btn.Owner.Items;
var index = items.IndexOf(btn);
for (int i = index + 1; i < items.Count; i++)
{
if (!(items[i] is ToolStripButton)) break;
((ToolStripButton)items[i]).Checked = false;
}
for (int i = index - 1; i > 0 && !(items[i] is ToolStripSeparator); i--)
{
if (!(items[i] is ToolStripButton)) break;
((ToolStripButton)items[i]).Checked = false;
}
}
这一方法让工具栏中的按钮以单选按钮的形式出现。当它们其中一个被点击后,同分组内的其他按钮将失效。
应用几乎完成了。你可以现在就运行它,测试应用的不同视图和过滤能力,就像下图中显示的那样:
这个视图中显示所有产品的销售额,通过月份和国家进行分组。请注意图表中是如何显示接近$300,000的数值。
如果你单击”$$$ Expensive”按钮,过滤器将应用到视图中,你立即就能发现改变。请注意现在图表中是如何显示接近$80,000的数值。大额数值将占有三分之一的销售额。
应用最后缺少的部分是生成报表。用户已经能够将OlapGrid中的数据复制到Excel中,并且可以打印或者保存这些结果。但是我们可以让它变得更简单,允许他们直接通过应用打印或者创建PDF文件。
想要完成这一功能,让我们添加部分代码到句柄中。单击”Reprot…”按钮,代码十分简单:
void _btnReport_Click(object sender, EventArgs e)
{
using (var dlg = new C1.Win.Olap.C1OlapPrintPreviewDialog())
{
dlg.Document = c1OlapPrintDocument1;
dlg.StartPosition = FormStartPosition.Manual;
dlg.Bounds = this.Bounds;
dlg.ShowDialog(this);
}
}
如果你已经在.NET中完成过打印,代码应该与此类似。它首先实例化一个C1OlapPrintPreviewDialog对象。这个类类似于标准的PrintPreviewDialog类,但是它增加了导出PDF文件的能力。
代码中设置对话框的Document属性,初始化它的位置,然后显示对话框。如果你现在运行程序,单击”Report…”按钮,你将会看到一个像下面一样的对话框:
通过这个对话框,用户可以修改页面布局,打印或是导出PDF文件。