来自Craig Boyd很棒的工具,第二部份

来自 Craig Boyd 很棒的工具,第二部份
 
发布日期: FoxTalk 2.0 2006 五月
作者: Doug Hennig 翻译: Sunny
Doug Hennig 继续研究 Craig Boyd 放到 VFP 社区很棒的工具,和讨论他自己添加的代码帮助你能使这些工具在你的应用里更加好看。
上个月,我讨论了几个 VFP MVP 和领袖 Craig Boyd 慷慨地提供给 VFP 社区的工具,包括他的加密函数库,进度条控件和分割条控件。这个月,我们将看更多一些他的工具:日历控件和滚动容器控件。
日历控件
我用 Microsoft Date and Time Picker ActiveX 控件广阔地提供一种容易的方法让用户输入日期。它准备了一个类似文本框的控件用来输入日期,但在日期的各部分有自动的验证。也有一个向下箭头按钮,当按下时,显示一个日期,用户可以从视觉上选择日期。可是,这个控件有几个缺点:
因为它是一个 ActiveX 控件,所以你不得不要和你的应用一起安装和注册一个 OCX MSCOMCT2.OCX ),所以,如以编程序方式实例化它而不是拖它到表单上,你会遇到许可的问题,除非手动更新 Windows 注册表来安装这个控件的许可。
这个控件相当旧,所以我它不能在视觉上象时髦的控件那样显示,例如,它不支持 Windows XP 桌面主题。
它不允许空的日期,所以你必须写程序处理这样情况。
Craig 创建军一个纯 VFP 代码的日历类,可在 http://www.sweetpotatosoftware.com/files/vfpcalendar.zip 下载获得,由于它是 VFP 代码,所以没有 ActiveX 发布,因为是提供源代码的,如果你有需要可能修改控件的外观和功能。

1显示的是在Craig的下载里一个样例,在一个VFP表单面有一个日期控件。除了一个好看的日期外,注意6122127的格,有不同的突显,接下来我会讨论如何实现的。

1 Craig 的日历控件是完全用 VFP 写的好看的控件
 
除日历控件外, Craig 也提供了 Date and DateTime picker 控件,象微软控件一样,有一个文本框和一个下拉日历,但是视觉上更吸引人。图 2 显示了这些控件的一个样例。

2 比微软控件好看的 Date and DateTime picker 控件
要使用日历控件,从 VFPCalendar.VCX 拖出一个 CalMonth 实例到表单。 CalMonth 有几个属性控制它的外观,包括 CalendarBackColor CaptionForeColor DaysForeColor DaysOfTheWeekForeColor HighlightForeColor OverlayForeColor 。如果用编程方式改变这些属性的一个,你必须用一个有些神秘的步骤设置去保证日历是完全更新:调用 SetColors 方法,赋 CalEngine1 CurrentDate 属性给它自己(那样它的 Assign 方法激发,方法里有一些必要的处理),然后调用 Refresh ( 它看起象一个 CalMonth 的方法,例如 RefreshCalendar ,会使用这样更容易。 ) 另外一个重要的属性是 DateSelected ,包含了在日历里选了的日期,你可能想初始化为 DATE() 用为突显当天的日期,或者初始化为一个日期字段那它日期会自动被选上。在用户选了日期后,必然是使用那个属性的值。
Date and DateTime picker 类更容易,拖一个 CalDatePicker CalDateTimePicker 对象到表单上,然后设置 ControlSource 为期望值,例如一个日期或日期时间字段。控件的 Value 属性包含用户输入的日期或日期时间。
除我刚才提及的属性外,有其他方法控制日历外观,如用覆盖;但在我们讨论这个之前,让我们去看 Craig 如何构造这个日期控件的。 CalMonth 是一容器类,包含了一个标题容器,一个表格和一个非可视的驱动对象。没错,日历部分实际上是一个常规的 VFP 表格。表格设成没有网格线,没有滚动条,没记录或删除标记和没有表头。因此,在设计时,它甚至不像一个表格。这个表格有 7 列,每个列对象都包含一个 CalDay 对象。
CalDay 也是一个容器类。它由两个文本框和一个命令按钮组成。其中一个文本框 txtDay 显示日期。另外一个 txtOverlay 显示叠加的信息。两文本框的 When 方法都返回 .F. (实际上在父类实现),所以它们不会获得焦点。命令按钮不显示出来( Style 属性设为 1-Invisible ),但它的 MouseDown 方法选择日期,即表格的单元格。
表格表现得似一个日历的秘密是 CalDay BackStyle_Access 方法。 Craig 2005 8 19 在它的博客发布了详细解释它是如何工作 (http://www.sweetpotatosoftware.com/SPSBlog/default,date,2005-08-19.aspx) 。但简略解释表格列的每个对象的 BackStyle 属性被表格每个单元格访问,所以如果你为这个属性创建一个 Access 方法,它会被每个实例对象访问,给你完全的控制,胜过在逐个单元格显示对象的方式。为什么这样说,在图 1 ,一些单元格显示为星期几的列标题,一些显为不同颜色,和一些有“重叠”对象。我不会重头到尾解释 CalDay.BackStyle_Access 的代码,因为它是相当长的,但我会推荐你自己去检验它,正好看看这强大的技术是多么棒的。 Craig 在他的网站里有其他的示例,在表格的逐个单元格显示不同图象,或者用表格显示条形图(译者注:这是图表)。
因为这个日历是一个真正的表格,在这里表格必须是绑定一个游标( cursor )。 CalEngine 类,在 CalMonth 里包含一个它的实例,是负责创建那个游标。它的 CreateCursor 方法创建一个带如下列的游标:
• DayNum1 7 ,代表一个号数(日期的日),绑定表格的 1 7
• Overlay1 7 ,包含了在列中显示叠加字符,
• OverColor1 7 ,包含了用于叠加字符的前景色
• DayColor1 7 ,包含号数的前前景色
• BackColor1 7 ,包含号数的背景色
这样,在日历的每列有 5 个字段(如:第一列有 DayNum1, Overlay1, OverColor1, DayColor1, BackColor1 )和每行一条记录。用所有这些字段,你有许多的控制,胜过逐个单元格展现的方式。
例如, Craig 提供的一个样例 ExampleOptions.SCX ,覆盖了它的 CalMonth 实例的 CalEngine1 对象的 CreateCursor 方法,插入随机值到随机列的 OverColor, DayColor BackColor 字段。它也设了几个叠加字段为“ X ”或“ O ”。在图 1 6 号的单元格有一个“ X ”叠加, 21 号有一个“ O ”。你每次运行这个表单,它看起来都不同(有些事情在产品应用中我不推存的,但它这样只为了一个好的演示)。
Craig 的日历控件是每个 VFP 开发人员的工具箱非常好的新成员。
SFCalMonth
或许你知道,我总是喜欢把工具调整一点使它外观或性能适合我的需要, Craig 的日历也是不例外。我喜欢微软控件原因之一是它能快速地选择精确的月份:点击在日历标题里的月显示月份的选单,你可以从里面选择,而不必一次一个月地点击箭头按钮。
变更年份是比较笨重( clunkier )一点(译者:没办法读得书少不知道 clunkier 是什么意思):点击日历显示年份的上下按钮,一次加或减一年,那么,选择某人的出生年,呃哼,年龄,意味要许多次按钮的点击。幸运的,为月份增加一个选单到 CalMonth 是相当容易,我还增加一个选单给年份。
我做 CalMonth 的子类,生成了 SFCalMonth SFCalendar.VCX 里,简单地添加一个月份或年份的选单,方法是添加代码到 CalMonthMover1 控件的一些方法里( CalMonthMover1 是日历的标题)。不幸的,在 CalMonthMover1 里月份和份年的显示是在同一个的文本框里控制。我不想要写难看的必需的代码算出鼠标是否在月或年上面,所以我决定点击会显示月的选单和右击会显示年的选单。 Click 方法有如下代码:
local lnI, ;
 ldDate
define popup MonthPicker from mrow(), mcol()
for lnI = 1 to 12
 define bar lnI of MonthPicker ;
    prompt cmonth(date(2006, lnI, 1))
next lnI
on selection popup MonthPicker ;
 deactivate popup MonthPicker
activate popup MonthPicker
if not empty(prompt())
 ldDate = This.Parent.calengine1.currentdate
 This.Parent.calengine1.currentdate = ;
    date(year(ldDate), bar(), day(ldDate))
 This.setcaption()
endif not empty(prompt())
RightClick 里有类似的代码来显示年份,尽管我定了在日历里用当前年的过去 5 年到向前 5 年的范围。如果你工作需要一个更宽的范围,例如出生日期,你可以要不同的机制去快速选择年份。
我做的另外一个修改必须利用表格的特性。虽然 Craig 设置日历的表格的 AllowCellSelection 属性为 .F. ,阻止很多的表格的行为,但在某一方面那个表格的举止还是像一个表格,当你想它是一个日历时,这是令人不快的。例如:如果你在一个号数上点击后选择了它,然后按向下翻页键(心想可能会显示下个月的日历),表格滚动了,所以一个号数也没有显示。(译者注:我试了,按第一下向下翻页键没有任可反应,要按第二下才是这样。)我认为处理这个是非常容易的:简单地覆盖 KeyPress 方法,当是向下翻页的用 nodefault ,不幸的,那样不起作用,匆匆偷看了一下 KeyPress 的帮助主题说明了原因:如果 AllowCellSelection .F. KeyPress 方法被忽略(译者注:我看了 VFP9 SP1 的帮助,里面说的跟这里相反,“如果表格的 AllowCellSelection 属性为设为真( .T. ), VFP 不予处理表格的 KeyPress 事件和在单元格级的使用这个事件。”我还看了 Craig CalMonth 里表格的 AllowCellSelection .T. ,而不是 .F. ,作者的 SFCalMonth 的表格的 AllowCellSelection .F. ,而不是 .T. ),所以我不得不作出一些修改去支持这个:将 AllowCellSelection 设回 .T. ,然后手动处理日期选择。下面的代码是来自表格的 Click 方法,使再能用鼠标日期选择。注意它是用 GridHitTest 方法算出点击时的行和列——我承认,我之前没有用它,然后,它激发了相应列的按扭对象的 MouseDown 方法,它有处理日期选择的逻辑。
local lnWhere, ;
 lnRow, ;
 lnCol
lnWhere = 0
lnRow   = 0
lnCol   = 1
This.GridHitTest(mcol(Thisform.Name, 3), ;
 mrow(Thisform.Name, 3), @lnWhere, @lnRow, @lnCol)
if lnWhere = 3 and lnRow > 1
 go lnRow
 This.Columns(lnCol).CalDay1.CalButton1.MouseDown()
endif lnWhere = 3 ...
当我添加代码到 KeyPress 去处理向下翻页时,我考虑我也可以添加支持其他按键使这个日历控件更容易使用。向下翻页键移动日历向前一个月,当向上翻页键时移动向后一个月,左,右,上和下方向键在预期的方向移动选择的日期。
lparameters tnKeyCode, ;
 tnShiftAltCtrl
local ldDate, ;
 llDateChanged
with This.Parent
 do case
* Handle PgDn: move ahead one month.
    case tnKeyCode = 3
      ldDate = gomonth(.DateSelected, 1)
      llDateChanged = .T.
* Handle PgUp: move back one month.
    case tnKeyCode = 18
      ldDate = gomonth(.DateSelected, -1)
      llDateChanged = .T.
* Handle up arrow: move back a week.
    case tnKeyCode = 5
      ldDate = .DateSelected - 7
      llDateChanged = .T.
* Handle down arrow: move ahead a week.
    case tnKeyCode = 24
      ldDate = .DateSelected + 7
      llDateChanged = .T.
* Handle left arrow: move back a day.
    case tnKeyCode = 19
      ldDate = .DateSelected - 1
      llDateChanged = .T.
* Handle right arrow: move ahead a day.
    case tnKeyCode = 4
      ldDate = .DateSelected + 1
      llDateChanged = .T.
 endcase
* If the date was changed, handle it.
 if llDateChanged
    store ldDate to .calengine1.Currentdate, ;
      .DateSelected
    .Calmonthmover1.SetCaption()
    .Refresh()
    nodefault
 endif llDateChanged
endwith
 
滚动容器控件
在我的 2004 年十二月 FOXTALK 2.0 专栏里,“ Mining for Gold in XSource, Part 3”(在Xsource 里的金矿,第三部份),我讨论了包含在 VFP 自带的源码里一个用于任务清单的类,它在表单里提供了一个滚动的区域。因为它用 Microsoft Flat Scrollbar ActiveX 控件的,所以再次提一下,你必须肯定是安装和注册正确的 OCX 。还有像 Microsoft Date and Time Picke 控件,它不支持 Windows XP 风格。 Craig 创建了他自己的滚动容器控件,可从 http://www.sweetpotatosoftware.com/files/vfpscrollbar.zip 获得。

3展示了在运行中的这个控件。你可以看到我滚动窗口显示一个编辑框和一个页框的一部分,也有一个命令按钮。注意滚动条像XP风格。 

3 . 滚动条控件允许你在表单里创建一个滚动区域
有关这个控件使人有点兴奋的事情是根本没有图象的。所有是用 shape 绘制的:每个滚动条的末端的方框,滚动条的拇指,甚至在拇指中间的四条小线条。在他的博客里,关于这个类( 2005 8 27 发布),他说花了超过 5 个小时创建这个控件,和信任它。
构造成这个滚动容器的所有类都在 VFPScrollBar.VCX 里。主类是 SBScrollContainer 。它是一个分别在底部和右边的边缘带有水平和垂直滚动条的容器类。然而,你可以通过设置 Scrollbars 属性为 0- 没有, 1- 水平, 2- 垂直, 3 (默认)两者,来控制滚动条的可视。 ScrollableHeight ScrollableWidth 属性指定滚动区域的尺寸。
使用 SBScrollContainer ,拖它到一个表单上和添加你想在滚动区域内的控件到容器内。你可以调整 SBScrollContainer 为必要的尺寸,但要知道,在设计时它的组件不会自己重新配置的,所以它们看起来不是完全正确的。可是,当你在运行这个表单时,适应容器的尺寸,那些组件被调整到正确的尺寸和位置,甚至锚定,所以尺寸调整了,表单仍然正确地工作。
让我们更贴近去看看这个滚动容器控件,看 Craig 是如何实现的。 SBScrollContainer 是一个用了 ScrollbarHorizontal ScrollbarVertical 对象的容器。 Scrollbars_Assign 保证只有适当的组件可视, ScrollableHeight ScrollableWidth Assign 方法是分别为了设置 ScrollbarVertical ScrollbarHorizontal Max 属性。每个滚动条对象的 Change 方法调整各个被包含的对象的 Top Left 属性,所以它们看来像是滚动的。这个方法是当用户在滚动条里点击时激发的,点击滚动条的任一端,或拖动滚动条的拇指。
ScrollbarHorizontal ScrollbarVertical Scrollbar 的子类。这个类有些属性指示当用户点击按钮( SmallChange 默认是 25 )或滚动条( LargeChange 默认 100 )时滚动多少个像素。它也告诉你在滚动条里移动的范围( Min Max, 分别设置 0 500 )。 Value 属性指示了当前滚动的位置,它的 Assign 方法激发 Change 方法,所以它表现为一个事件。
滚动条类的天赋(或疯狂)是它们是如何构造的。正如我前面所说的,没有图象,所以在滚动条里出现的每一个边框和线条居然是由多个不同颜色和长度的形状( shape )组成,形成了一个 3D XP 风格的外观。我不打算深入研究这个构造的详细情况,你自己任何时候都可以很自由地到处刺探这些类并看 Craig 如何艰苦摆放每一条线和形状到特定的地方。这就象用牙签来坐桥梁模型,不过结果看起来却不错。
SFScrollContainer

再一次,做Mr. Picky(挑剔),我有一个关于SBScrollContainer的遁辞。如果你运行来自Craig的下载里的样例,你会留意到当你滚动时,可以在水平的滚动条的右边和垂直的滚动条下面的那小小的方形区域里看到页框的一部份。你可能在我已经在图4标出的区域里看到这个。

4 当你滚动时,在滚动区的一个角落里控件被渗漏了
幸运的,这是容易完善的。我建了一个 SBScrollContainer 的子类,在 SFScrollbar.VCX 里叫 SFScrollContainer 。我在泄露的区域添加了下小小的 shape 。这人 shape 盖住了在滚动容器的“洞”,所以它包含的控件没有在那个地方显示出来。我添加了如下代码到 PositionScrollbars 方法,从 Setup 里调用, Setup 本身被 Init 调用,这个方法( PositionScrollbars )保证这个滚动条组件(指 shape )被调节为适当的尺寸和位置,所以这些代码为这个 shape 完成任务的。
dodefault()
with This
 .Shape1.Left   = .scrollbarhorizontal1.Width - 1
 .Shape1.Top    = .scrollbarvertical1.Height - 1
 .Shape1.Width = .Width - .Shape1.Left
 .Shape1.Height = .Height - .Shape1.Top
 .Shape1.Anchor = 12
endwith
去看 SFScrollContainer 的一个样例,运行 SFExample.SCX 。它是 Craig Example.SCX 的克隆,但这里是用了 SFScrollContainer 而不是 SBScrollContainer
SBScrollContainer 只剩余一个问题了,然而,我没有完善的它:如果你滚动容器,那么控件被滚动条遮住了部分,你可以令它自已在滚动条里渗透出来。例如,滚动样例表单,使用命令按钮是的一半可见,然后盘旋鼠标指针到按钮上面,你会见到那个按钮完全被绘制和遮住了滚动条的一部分。改变控件的 ZOrder 属性也没帮助。
总结

在这篇文章里,我看了Craig Boyd提供的两个工具:日历控件和滚动容器。有另一个有趣的控件,给VFP开发人员提供模仿XP的面板(panel)的,但我即将休假,那只能作为以后的主题了。

作者源程序:http://www.pinpub.com/Media/PublicationsArticle/605HENNIG.zip

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值