图像与布局管理的综合指南
1. 创建和显示图像
在处理图像时,我们可以使用
beginWaitCursor()
的事务形式,当代码块退出时,它会自动调用
endWaitCursor()
。示例代码如下:
open_button = FXButton.new(self, "Open File")
open_button.connect(SEL_COMMAND) do
app.beginWaitCursor do
open_file # this may take awhile...
end
end
需要注意的是,在这个例子中,当程序忙于打开文件时,用户无法对程序进行其他操作。如果任务耗时较长但可以在后台执行,建议在单独的线程中执行该任务,以保持程序的响应性。
创建
FXImage
对象的简单方法是使用特定格式的子类,如
FXJPGImage
直接从图像数据构建图像。以下是从 JPEG 文件构建图像的代码示例:
birdsnest_image =
FXJPGImage.new(app, File.open("birdsnest.jpg", "rb").read)
如果图像数据在线上,可使用 Ruby 的标准
open-uri
库获取:
require 'open-uri'
oscar_image =
FXJPGImage.new(app, open("http://tinyurl.com/35o8yy").read)
常见图像文件格式及其对应的
FXImage
子类如下表所示:
| 图像文件格式 | 类名 |
| — | — |
| Windows Bitmap (BMP) | FXBMPImage |
| Graphics Interchange Format (GIF) | FXGIFImage |
| Joint Photographic Experts Group (JPEG) | FXJPGImage |
| Portable Network Graphics (PNG) | FXPNGImage |
| Tagged Image File Format (TIFF) | FXTIFImage |
获取
FXImage
对象引用后,有多种使用方式。
FXImageFrame
类是
FXFrame
的子类,用于显示图像:
FXImageFrame.new(tab_book, birdsnest_image,
:opts => FRAME_RAISED|FRAME_THICK|LAYOUT_FILL)
FXImageFrame
不太复杂,当调整窗口大小时,图像不会缩小以适应新窗口大小,只会在边缘被裁剪。显示未知大小的图像时,
FXImageView
更合适,它在滚动窗口中显示图像:
FXImageView.new(tabbook_page, oscar_image, :opts => LAYOUT_FILL)
2. 操作图像数据
在某些情况下,需要对图像数据进行小修改。
FXImage
类提供了一些支持图像数据操作和转换的 API,但设置需要额外的步骤。默认情况下,
FXImage
创建服务器端表示后会丢弃原始图像数据的副本。如果需要在调用
create()
后操作图像数据,需在构建图像时传入
IMAGE_KEEP
选项:
@image = FXJPGImage.new(app,
File.open("birdsnest.jpg", "rb").read,
:opts => IMAGE_KEEP)
可以使用
crop()
方法裁剪图像,例如保留图像的上半部分:
@image.crop(0, 0, 0.5*@image.width, 0.5*@image.height)
crop()
方法的前两个参数是要保留区域的左上角坐标,后两个参数是该区域的宽度和高度。
还可以使用
mirror()
方法创建图像的镜像:
@image.mirror(false, true)
第一个参数表示是否水平镜像,第二个参数表示是否垂直镜像。
许多图像操作 API(如
scale()
、
crop()
和
mirror()
)在完成像素重排后会自动调用
render()
方法更新服务器端表示,但
blend()
和
gradient()
等 API 不会,需要手动调用
render()
。
3. 创建和显示图标
图像和图标的主要区别在于,图标的某些部分被视为透明。可以为图标定义透明颜色,绘制图标时,该颜色像素下方的内容会显示出来。图标通常比普通图像小,但大小没有严格限制,且图标可用于更多地方。
创建图标的简单方法是使用
FXIcon
的子类从图像数据构建图标。例如,从 GIF 文件构建图标的代码如下:
gif_icon = FXGIFIcon.new(app, File.open("fxruby.gif", "rb").read)
大多数图像文件格式(除 GIF 外)本身不支持单一透明颜色的概念。构建 BMP 图标时,可使用
IMAGE_ALPHAGUESS
选项让 FOX 根据图像四角颜色猜测透明颜色:
r_icon = FXBMPIcon.new(app,
File.open("Rasl.bmp", "rb").read,
:opts => IMAGE_ALPHAGUESS)
如果
IMAGE_ALPHAGUESS
算法猜测不准确,可以通过传入颜色值和
IMAGE_ALPHACOLOR
选项明确指定透明颜色:
red_transp = FXBMPIcon.new(app,
File.open("shapes.bmp", "rb").read,
:opts => IMAGE_ALPHACOLOR)
red_transp.transparentColor = FXRGB(255, 0, 0)
PNG 文件格式包含 alpha 通道,可指定每个像素的透明度。FOX 渲染此类图标时,非完全透明(alpha 值非零)的像素会显示。
4. 布局管理基础
布局管理器负责安排其他小部件的位置和大小,为用户界面的组织提供了很大的灵活性。它不仅能确定单个小部件的大小和位置,还能处理小部件组之间的相对大小和位置。对于无法同时显示在同一屏幕上的情况,布局管理器提供了条件显示不同部分用户界面的选项。
我们先从包装模型开始了解,
FXPacker
布局管理器的工作方式类似于俄罗斯方块游戏。构建
FXPacker
窗口时,它就像一个空盒子,添加的第一个子小部件会被打包到其四个边之一。通过传入
LAYOUT_SIDE_TOP
、
LAYOUT_SIDE_RIGHT
、
LAYOUT_SIDE_BOTTOM
或
LAYOUT_SIDE_LEFT
作为布局提示来指定打包的边。例如:
packer = FXPacker.new(self, :opts => LAYOUT_FILL)
child1 = FXButton.new(packer, "First",
:opts => BUTTON_NORMAL|LAYOUT_SIDE_BOTTOM)
默认情况下,按钮会与
FXPacker
的左侧对齐,可通过
LAYOUT_CENTER_X
或
LAYOUT_RIGHT
改变对齐方式:
child1 = FXButton.new(packer, "First",
:opts => BUTTON_NORMAL|LAYOUT_SIDE_BOTTOM|LAYOUT_CENTER_X)
还可以使用
LAYOUT_FILL_X
使按钮水平拉伸:
child1 = FXButton.new(packer, "First",
:opts => BUTTON_NORMAL|LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X)
当尝试将两个按钮打包到
FXPacker
的底部时,会发现布局可能不符合预期。为实现更复杂的布局,通常需要嵌套布局管理器。
5. 使用水平和垂直框架创建简单布局
FXHorizontalFrame
和
FXVerticalFrame
是
FXPacker
的子类,对包装模型施加了额外的约束。
FXHorizontalFrame
水平排列子小部件,
FXVerticalFrame
垂直排列子小部件。
默认情况下,
FXHorizontalFrame
从左到右排列子小部件:
hframe = FXHorizontalFrame.new(self)
child1 = FXButton.new(hframe, "First")
child2 = FXButton.new(hframe, "Second")
child3 = FXButton.new(hframe, "Third")
可以使用
LAYOUT_LEFT
和
LAYOUT_RIGHT
指定子小部件的对齐方式:
hframe = FXHorizontalFrame.new(self)
child1 =
FXButton.new(hframe, "Right", :opts => BUTTON_NORMAL|LAYOUT_RIGHT)
child2 =
FXButton.new(hframe, "Left",
:opts => BUTTON_NORMAL|LAYOUT_LEFT)
使用
PACK_UNIFORM_HEIGHT
提示可使框架中的所有小部件具有相同的高度:
vframe = FXVerticalFrame.new(self, :opts => PACK_UNIFORM_HEIGHT)
child1 = FXButton.new(vframe, "This is a short button")
child2 = FXButton.new(vframe, "This one is a\nlittle taller")
child3 = FXButton.new(vframe, "This button is\nthree lines\ntall")
也可以同时使用
PACK_UNIFORM_WIDTH
和
PACK_UNIFORM_HEIGHT
使小部件具有统一的宽度和高度。
6. 嵌套布局管理器
为了实现将两个按钮放在
FXPacker
左下角和右下角的布局,我们可以将两个按钮放在
FXHorizontalFrame
中,再将该框架放在
FXPacker
中:
packer = FXPacker.new(self, :opts => LAYOUT_FILL)
hframe = FXHorizontalFrame.new(packer, :opts => LAYOUT_SIDE_BOTTOM)
child1 = FXButton.new(hframe, "Bottom-Right",
:opts => BUTTON_NORMAL|LAYOUT_RIGHT)
child2 = FXButton.new(hframe, "Bottom-Left",
:opts => BUTTON_NORMAL|LAYOUT_LEFT)
但这样按钮会紧挨着左侧,问题在于
FXHorizontalFrame
的布局提示。我们可以通过指定
LAYOUT_FILL_X
来解决:
packer = FXPacker.new(self, :opts => LAYOUT_FILL)
hframe = FXHorizontalFrame.new(packer,
:opts => LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X)
child1 = FXButton.new(hframe, "Bottom-Right",
:opts => BUTTON_NORMAL|LAYOUT_RIGHT)
child2 = FXButton.new(hframe, "Bottom-Left",
:opts => BUTTON_NORMAL|LAYOUT_LEFT)
7. 创建固定布局
可以绕过布局管理器使用固定布局,明确指定小部件的位置和/或大小。例如:
@fixed_pos_button = FXButton.new(self, "Fixed Position",
:opts => BUTTON_NORMAL|LAYOUT_FIX_X|LAYOUT_FIX_Y,
:x => 20, :y => 20)
@fixed_size_label = FXLabel.new(self, "Fixed Size Label",
:opts => (LAYOUT_CENTER_X|LAYOUT_CENTER_Y|
LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT),
:width => 120, :height => 40)
也可以使用更紧凑的
LAYOUT_EXPLICIT
提示:
FXLabel.new(self, "Fixed Position and Size",
:opts => LAYOUT_EXPLICIT,
:x => 20, :y => 20,
:width => 120, :height => 40)
然而,固定布局几乎不是最佳解决方案。它会降低用户界面在不同显示特性运行环境中的可移植性,可能导致在不同字体或屏幕分辨率下显示异常。除非使用始终需要相同大小的元素(如固定大小的图像或图标),否则应避免在应用程序中使用固定布局。
8. 调整填充和间距
在打包小部件时,小部件与容器之间以及相邻小部件之间会有一定的间距。可以通过调整填充和间距参数来优化布局。
小部件的填充是其边缘周围预留的空间,大多数小部件的默认填充为每边 2 像素。可以在构建小部件时使用
:padLeft
、
:padRight
、
:padTop
和
:padBottom
键指定填充值,也可以使用
:padding
一次性设置所有边的填充为零:
spinner = FXSpinner.new(self, 4,
:opts => SPIN_NORMAL|LAYOUT_SIDE_TOP|LAYOUT_LEFT,
:padLeft => 0, :padTop => 0)
spinner = FXSpinner.new(self, 4,
:opts => SPIN_NORMAL|LAYOUT_SIDE_TOP|LAYOUT_LEFT,
:padding => 0)
构建小部件后,也可以使用
padLeft
、
padRight
、
padTop
和
padBottom
属性更改填充值。
布局管理器的水平和垂直间距参数决定了相邻子小部件之间的间距。可以在构建布局管理器时使用
:hSpacing
和
:vSpacing
键指定间距值:
groupbox = FXGroupBox.new(self, "No horizontal or vertical spacing",
:hSpacing => 0, :vSpacing => 0)
构建后也可以使用
hSpacing
和
vSpacing
属性更改间距值。
需要注意的是,间距只在相邻小部件之间起作用,布局管理器边缘的小部件不受间距影响。同时,要记住在布局管理器上指定间距,在子小部件上指定填充。由于所有布局管理器都可以作为其他布局管理器的子项,因此也可以为布局管理器指定填充。
9. 特殊用途布局管理器
除了前面介绍的基本布局管理器,还有一些特殊用途的布局管理器,它们能满足更复杂的布局需求。
9.1 FXMatrix
FXMatrix
是一个按行列排列子部件的布局管理器。可以指定矩阵的行数和列数,子部件会按顺序填充到矩阵的单元格中。以下是一个简单的示例:
matrix = FXMatrix.new(self, 2) # 创建一个2列的矩阵
button1 = FXButton.new(matrix, "Button 1")
button2 = FXButton.new(matrix, "Button 2")
button3 = FXButton.new(matrix, "Button 3")
button4 = FXButton.new(matrix, "Button 4")
在这个示例中,四个按钮会被排列成一个 2 行 2 列的矩阵。
9.2 FXScrollWindow
FXScrollWindow
用于创建一个带有滚动条的窗口,当子部件的大小超出窗口大小时,可以通过滚动条查看全部内容。示例代码如下:
scroll_window = FXScrollWindow.new(self)
frame = FXHorizontalFrame.new(scroll_window)
# 添加多个子部件到 frame 中,可能会超出窗口大小
for i in 1..10
button = FXButton.new(frame, "Button #{i}")
end
在这个例子中,当添加的按钮数量过多,超出
FXScrollWindow
的显示范围时,就会出现滚动条。
9.3 FXSplitter
FXSplitter
允许用户通过拖动分隔条来调整子部件的大小。可以水平或垂直分割空间,示例如下:
splitter = FXSplitter.new(self, :opts => LAYOUT_FILL)
left_frame = FXHorizontalFrame.new(splitter)
right_frame = FXHorizontalFrame.new(splitter)
在这个示例中,
FXSplitter
将窗口水平分割成两部分,用户可以拖动分隔条来调整左右两个框架的大小。
10. FXTabBook 布局管理器
FXTabBook
用于创建标签式的笔记本视图,用户可以通过点击标签在不同的页面之间切换。以下是一个简单的创建
FXTabBook
的示例:
tab_book = FXTabBook.new(self, :opts => LAYOUT_FILL)
page1 = tab_book.createPage("Page 1")
button1 = FXButton.new(page1, "Button on Page 1")
page2 = tab_book.createPage("Page 2")
button2 = FXButton.new(page2, "Button on Page 2")
在这个示例中,创建了一个包含两个页面的
FXTabBook
,每个页面上都有一个按钮。用户可以通过点击标签在两个页面之间切换。
11. 解决常见布局问题的策略
在实际开发中,我们会遇到各种布局问题,以下是一些解决常见布局问题的策略:
11.1 合理嵌套布局管理器
对于复杂的布局,嵌套布局管理器是一个有效的解决方案。例如,要实现一个包含顶部菜单、左侧导航栏、中间内容区域和底部状态栏的布局,可以使用以下嵌套方式:
# 创建主窗口
main_window = FXMainWindow.new(app, "Complex Layout", :width => 800, :height => 600)
# 垂直框架用于整体布局
vframe = FXVerticalFrame.new(main_window, :opts => LAYOUT_FILL)
# 顶部菜单
menu_bar = FXMenuBar.new(vframe, :opts => LAYOUT_FILL_X)
# 水平框架用于中间部分
hframe = FXHorizontalFrame.new(vframe, :opts => LAYOUT_FILL)
# 左侧导航栏
nav_frame = FXVerticalFrame.new(hframe, :width => 200)
# 中间内容区域
content_frame = FXHorizontalFrame.new(hframe, :opts => LAYOUT_FILL)
# 底部状态栏
status_bar = FXStatusLine.new(vframe, :opts => LAYOUT_FILL_X)
通过这种嵌套方式,可以清晰地组织各个部分的布局。
11.2 动态调整布局
在某些情况下,需要根据用户的操作或数据的变化动态调整布局。例如,当用户点击按钮时,显示或隐藏某些部件。可以通过设置部件的可见性来实现:
button = FXButton.new(self, "Toggle Visibility")
hidden_frame = FXHorizontalFrame.new(self, :opts => LAYOUT_FILL, :visible => false)
button.connect(SEL_COMMAND) do
hidden_frame.visible = !hidden_frame.visible
hidden_frame.layout
end
在这个示例中,点击按钮会切换
hidden_frame
的可见性,并重新布局。
11.3 响应不同屏幕分辨率
为了确保应用程序在不同屏幕分辨率下都能正常显示,应避免使用固定布局,尽量使用相对布局和布局提示。例如,使用
LAYOUT_FILL_X
和
LAYOUT_FILL_Y
让部件填充可用空间。同时,可以根据屏幕分辨率动态调整布局参数,如填充和间距。
12. 总结
布局管理是创建美观、易用的用户界面的关键。通过合理使用各种布局管理器,如
FXPacker
、
FXHorizontalFrame
、
FXVerticalFrame
等,以及特殊用途的布局管理器,如
FXMatrix
、
FXScrollWindow
、
FXSplitter
和
FXTabBook
,可以实现各种复杂的布局。同时,要注意避免使用固定布局,合理调整填充和间距,以提高应用程序的可移植性和响应性。在实际开发中,根据具体需求选择合适的布局管理器和布局策略,能够有效地解决各种布局问题,为用户提供良好的视觉体验。
以下是一个简单的布局管理流程 mermaid 流程图:
graph LR
A[确定布局需求] --> B[选择布局管理器]
B --> C{是否复杂布局}
C -- 是 --> D[嵌套布局管理器]
C -- 否 --> E[使用单一布局管理器]
D --> F[调整填充和间距]
E --> F
F --> G[测试和优化布局]
通过遵循这些原则和策略,我们可以创建出适应不同场景和用户需求的优秀用户界面。
超级会员免费看
1377

被折叠的 条评论
为什么被折叠?



