DataWindow 魔法:主-明细对象
揭开对象的神秘面纱。
我写过的最有用的自定义对象之一就是我的 master_detail
对象。它在我工具箱中占有重要位置。本文将指导您完成编写这样一个对象的步骤,并在此过程中揭开该对象的神秘面纱。
首先,我们想要一些看起来像图 1 的东西。选择上面的 DataWindow 中的一行将在下面的 DataWindow 中显示详细信息。我敢肯定您已经见过这种 DataWindow,并且可能已经写过很多了。这里的关键不是告诉您如何编写这个,而是如何简化您将来编写的每一个。在我们的例子中,我将再次使用 PowerBuilder 附带的示例数据库。
要构建控件,我首先需要创建一个新的自定义对象。我点击菜单中的“文件”-“新建”或直接按 Ctrl-N,然后会出现图 1 中的对话框。我已经圈出了您需要点击的内容,以便您轻松找到。
完成此操作后,您将看到一个类似于小型窗口绘制器的界面。由于我喜欢使用白色背景,所以我将更改它。然后,我将放置两个 DataWindow 控件(我将使用我工具箱中的 u_dw),并将它们的大小调整到通常用于主从详细信息控件的大小。
我添加了一些按钮,最后得到了一个看起来像图 2 的东西。现在,我们只需在对象中添加少量代码,就可以开始了。
此对象唯一的真正限制是主表和从表必须具有完全相同的数据源。我们将通过为其中一个创建数据源,将其转换为语法,然后粘贴到第二个 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。这太难看了,不能给用户看。让我们稍微美化一下。
由于 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 字段(主键)设置为自动递增。当然,我必须先删除主键,然后更改列,然后重新创建主键。现在,键会自动处理,我们的“新建”按钮也正常工作了。
保存自动工作。
删除自动工作。
最后一步是简单地将 U_customer_editor
拖到 w_main
上并运行您的程序。
哦……还有最后一件事 让我们看看带有参数的检索是否有效。让我们创建一个销售报告,该报告检索一个日期范围之间的数据。正如我们已经指出的,两个数据源必须相同。让我们创建一个查询。我们可以这样做,或者创建一个存储过程。由于查询的使用频率较低,让我们使用它。只需点击“文件”-“新建”,然后点击“数据库”和“查询”。看看图 5。
这是 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。
当然,这个主从结构并不适合您的所有需求,随着时间的推移,它可能会有所扩展。我建议,与其将将来的代码放在父对象中,不如继承新的专业化主从对象,这样只有在需要时才需要承担开销。
最后,是的,我们应该在对象上设置数据编辑掩码,让用户输入日期,但我的文章篇幅有限,所以我只能留给您了。您不介意吧?