来自
Craig Boyd
很棒的工具,第二部份
发布日期:
FoxTalk 2.0 2006
五月
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表单面有一个日期控件。除了一个好看的日期外,注意6,12,21和27的格,有不同的突显,接下来我会讨论如何实现的。
图
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