12、图像与布局管理的综合指南

图像与布局管理的综合指南

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[测试和优化布局]

通过遵循这些原则和策略,我们可以创建出适应不同场景和用户需求的优秀用户界面。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值