65.9K
CodeProject 正在变化。 阅读更多。
Home

DataWindow 魔法:主-明细对象

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2012年1月20日

CPOL

8分钟阅读

viewsIcon

22852

downloadIcon

354

揭开对象的神秘面纱。

我写过的最有用的自定义对象之一就是我的 master_detail 对象。它在我工具箱中占有重要位置。本文将指导您完成编写这样一个对象的步骤,并在此过程中揭开该对象的神秘面纱。

首先,我们想要一些看起来像图 1 的东西。选择上面的 DataWindow 中的一行将在下面的 DataWindow 中显示详细信息。我敢肯定您已经见过这种 DataWindow,并且可能已经写过很多了。这里的关键不是告诉您如何编写这个,而是如何简化您将来编写的每一个。在我们的例子中,我将再次使用 PowerBuilder 附带的示例数据库。

Rik-Brooks-fig-1_0.jpg

要构建控件,我首先需要创建一个新的自定义对象。我点击菜单中的“文件”-“新建”或直接按 Ctrl-N,然后会出现图 1 中的对话框。我已经圈出了您需要点击的内容,以便您轻松找到。

完成此操作后,您将看到一个类似于小型窗口绘制器的界面。由于我喜欢使用白色背景,所以我将更改它。然后,我将放置两个 DataWindow 控件(我将使用我工具箱中的 u_dw),并将它们的大小调整到通常用于主从详细信息控件的大小。

我添加了一些按钮,最后得到了一个看起来像图 2 的东西。现在,我们只需在对象中添加少量代码,就可以开始了。

Rik-Brooks-fig-2_0.jpg

此对象唯一的真正限制是主表和从表必须具有完全相同的数据源。我们将通过为其中一个创建数据源,将其转换为语法,然后粘贴到第二个 DataWindow 的数据源中来实现这一点。我们稍后将实际进行操作。

这里有一个小问题。我们希望数据源相同,但我们希望在父对象中检索它。也就是说,在 u_master_detail 中。如果我们从不需要检索参数,这将很容易实现。我们只需要在对象的构造函数中添加以下内容:

Dw_master.setTransObject(sqlca)
Dw_master.sharedata(dw_detail)
Dw_master.retrieve()

我们遇到的问题是,我们会不时地想要使用检索参数。我们该如何处理呢?好吧,我们别无选择;我们必须允许程序员调用他们的检索函数。不过,我们确实想为他们处理 setTransObject 和 shareData。我们只需要让他们在构造函数中调用他们的检索。如果我们把代码放在构造函数中,它就会在他们的代码之前执行。

U_master_detail::constructor
// DESCRIPTION - Does the setup for the Datawindows

int ll_status
ll_status = dw_master.setTransObject(sqlca)
ll_status = dw_master.sharedata( dw_detail)

// found in u_dw
dw_master.of_selection_mode( "listbox")

现在,我们的程序员可以在其对象的构造函数中调用他的检索。他不必担心 setTransObject 或 sharedata。我们稍后会看到实际效果。我想花一点时间谈谈 of_selection_mode。您会在本文附带的 u_dw 对象中找到该函数。它是我个人工具箱的一部分。在这种情况下,我正在告诉 DataWindow 作为列表框运行,一次只突出显示一行。问题是,当我们调用 of_selection_mode 时,检索尚未执行,因此 DataWindow 将没有突出显示的行。我们将不得不依赖程序员来处理这个问题。

注意 sharedata 函数。这意味着您不必为 dw_detail 调用检索,因为它与主表共享数据。行的数量和顺序将完全相同。此外,如果您更新其中一个(任何一个),您都会更新另一个。

同步行

同步行很简单。在主表的 clicked 事件中,我只需滚动到从表中的同一行并将其设为焦点。我还将列设置为第一列。

U_master_detail.dw_master::clicked
// DESCRIPTION - Scrolls to the row in the detail

if row > 0 then
dw_detail.scrolltorow( row)
dw_detail.setcolumn( 1)
dw_detail.setfocus( )
end if

我检查了行数(事件的一个参数),因为我知道如果行数小于一,我们将遇到一个导致应用程序终止的错误。即使这永远不应该发生,对我来说也足够值得检查了。

接下来,我在从表中滚动到该行。这应该很明显。

然后我将列设置为 1。这意味着当从表获得焦点时,光标将位于第一列。否则,光标将位于上一行使用的任何列。如果我更改了邮政编码,光标仍会在邮政编码上,这就很麻烦了。

最后将焦点设置回从表。

Pb_new

在不需要了解任何 DataWindow 细节的情况下创建新行是完全可能的。以下是操作方法:

U_master_detail.pb_new::clicked()
// DESCRIPTION - Inserts a row and puts focus on the detail, first column
long ll_row
ll_row = dw_detail.insertRow(0)
dw_detail.scrolltorow(ll_row)
dw_detail.setcolumn( 1)
dw_detail.setfocus( )

请注意,在我们的例子中,主键是一个整数,但不是自动递增的。所以这意味着使用 new 我将必须以编程方式创建 ID。我不能在父对象中这样做。我们将在文章后面处理这个问题。

Pb_save

保存非常简单。

U_master_detail.pb_save::clicked()
// DESCRIPTION - Saves the datawindow
dw_detail.accepttext( )
dw_detail.update( )

Pb_delete

U_master_detail.pb_delete::clicked()
// DESCRIPTION - deletes the current row.
long ll_row
ll_row = dw_detail.getRow()
if ll_row > 0 then dw_detail.deleterow( ll_row)

这样就完成了父对象。下一步是创建我们示例所需的两个 DataWindow 对象。

D_customer_detail

客户详细信息 DataWindow 将具有与列表相同的数据源。我在文章前面已经提到了这一点。为了确保两个数据源完全相同,我转到 d_customer_list,然后从菜单中选择“设计”-“数据源”。在那里,我转到“设计”-“转换为语法”。我复制它,然后将其粘贴到 d_customer_detail 的数据源中。

在我们的例子中,这是两个 DataWindow 的 SQL。

SELECT
"contact"."phone",
"contact"."last_name",
"contact"."first_name",
"contact"."title",
"contact"."street",
"contact"."city",
"contact"."state",
"contact"."zip",
"contact"."fax",
"contact"."id"
FROM
"contact"
ORDER BY
"contact"."last_name" ASC,
"contact"."first_name" ASC

请注意,在这种情况下,列的顺序很重要。如果您回头看 pb_new 的 clicked 事件,您会发现有一个 setColumn 函数。我这样做是为了能够将光标设置为第一列。列表的 clicked 事件中也调用了相同的函数。为了使 setColumn 调用起作用,该列编号必须具有制表符顺序。由于 ID 是表中的第一列,因此也是 PowerBuilder 默认提供的 SQL 语句,我们必须手动移动该列。

这个 DataWindow 将是 Free Form 演示形式。当您进入绘制器时,会有一些工作要做。看看图 3。这太难看了,不能给用户看。让我们稍微美化一下。

Rik-Brooks-fig-3_0.jpg

由于 ID 是主键,让我们将其从屏幕上删除,然后添加一个计算字段。

接下来,我们只需格式化其余列,并下划线所有可编辑的列。最后,我们将制表符顺序设置为一个合理的数字。

我们现在快要结束了。继承自 u_master_detail,并将其保存到您的主 pbl 中,命名为 u_customer_editor。现在打开 u_customer_editor 并打开构造函数。

U_customer_editor::constructor
// DESCRIPTION - Does the retrieve
int ll_status
ll_status = dw_master.retrieve()

现在我们将列表 d_customer_list 设置为 dw_master,将 d_customer_detail 设置为 dw_detail。这里实际上没有什么需要编写代码的。唯一的问题是联系人表的主键。按照我们的设计,主键将为 null。为了使此工作正常,我们需要用户创建主键并输入它。用户必须知道要输入哪个键。或者,我们必须以编程方式处理它。我们可以处理,但有更简单的方法。

我只是将联系人表中的 ID 字段(主键)设置为自动递增。当然,我必须先删除主键,然后更改列,然后重新创建主键。现在,键会自动处理,我们的“新建”按钮也正常工作了。

Rik-Brooks-fig-4_0.jpg

保存自动工作。

删除自动工作。

最后一步是简单地将 U_customer_editor 拖到 w_main 上并运行您的程序。

哦……还有最后一件事 让我们看看带有参数的检索是否有效。让我们创建一个销售报告,该报告检索一个日期范围之间的数据。正如我们已经指出的,两个数据源必须相同。让我们创建一个查询。我们可以这样做,或者创建一个存储过程。由于查询的使用频率较低,让我们使用它。只需点击“文件”-“新建”,然后点击“数据库”和“查询”。看看图 5。

Rik-Brooks-fig-5_0.jpg

这是 SQL 的样子:

SELECT "sales_order"."id",
"sales_order"."cust_id",
"sales_order"."order_date",
"sales_order"."fin_code_id",
"sales_order"."region",
"sales_order"."sales_rep",
"sales_order_items"."line_id",
"sales_order_items"."prod_id",
"sales_order_items"."quantity",
"sales_order_items"."ship_date",
"product"."name"
FROM "sales_order",
"sales_order_items",
"product"
WHERE ( "sales_order_items"."id" = "sales_order"."id" ) and
( "sales_order_items"."prod_id" = "product"."id" ) and
( ( "sales_order"."order_date" between :from_date and :to_date ) )
ORDER BY "sales_order"."order_date" ASC,
"sales_order"."id" ASC

基本上,这将创建一个报告,其中包含一个日期范围内的所有销售订单,并将显示每个订单的商品和每个产品的名称。

现在我们从中创建主从 DataWindow。我不希望在主表或从表中出现所有行,事实上,在主表中,我将删除所有行,然后添加一个计算字段。然后,我将只为从表选择几个相关的列。为了使此报告有用,我可能需要添加销售人员,但我不是在演示一个好的销售报告,我是在演示您的新主从对象。

一旦我有了它们,我就继承自 u_master_detail。由于这是一个只读报告,我将通过将三个按钮的可见属性设置为 FALSE 来使它们不可见。

然后,我只需转到事件的构造函数,然后执行我的检索,包括参数。这是代码:

U_sales_report_master_detail.constructor
date from_date, to_date
from_date = date("1/1/2004")
to_date = date("12/31/2004")
dw_master.retrieve(from_date, to_date)

我们保存对象(我将其命名为 u_sales_report_master_detail)并关闭它。现在我们只需要将其放在 w_main 上,无需编写任何代码,然后运行它。您的窗口应该看起来像图 6。

Rik-Brooks-fig-6_0.jpg

当然,这个主从结构并不适合您的所有需求,随着时间的推移,它可能会有所扩展。我建议,与其将将来的代码放在父对象中,不如继承新的专业化主从对象,这样只有在需要时才需要承担开销。

最后,是的,我们应该在对象上设置数据编辑掩码,让用户输入日期,但我的文章篇幅有限,所以我只能留给您了。您不介意吧?

© . All rights reserved.