Lanterna的使用第四部分

教程 4
在第四篇教程中,我们最终将着眼于创建一个多窗口文本 GUI,所有这些都基于文本。就像上Screen一篇教程中的-layer是基于底层的一样Terminal,我们这里使用的GUI类都是建立在Screen界面之上的。因此,如果您使用这些类,则永远不应与直接支持 GUI 的底层 Screen 交互,因为它可能会以 GUI 不知道的方式修改屏幕。

GUI 系统是围绕一个通常是静态的但可以有组件和多个窗口的背景表面设计的。推荐的做法是让所有窗口都模态化,而不是让用户在窗口之间切换,但后者也可以做到。通过使用确定每个组件位置的布局管理器将组件添加到窗口。

DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory();
Screen screen = null;

try {
该类DefaultTerminalFactory不提供任何用于创建文本 GUI 的辅助方法,您需要Screen像我们在上一个教程中所做的那样启动它,以便将终端置于私有模式。

screen = terminalFactory.createScreen();
screen.startScreen();
有几个不同的构造函数MultiWindowTextGUI,我们将使用大多数这些值的默认值。要考虑的一件事是线程。使用默认选项,lanterna 将对所有 UI 操作使用调用线程,这意味着您基本上让调用线程阻塞,直到 GUI 关闭。TextGUIThread如果您希望 Lanterna 创建一个专用的 UI 线程而不锁定调用者,则可以使用一个单独的实现。就像使用 AWT 和 Swing 一样,您应该安排任何类型的 UI 操作以始终在 UI 线程上运行,但如果您尝试从另一个线程改变 GUI,Lanterna 会尽力而为。另一个将应用的默认设置是 GUI 的背景将是纯蓝色。

final WindowBasedTextGUI textGUI = new MultiWindowTextGUI(screen);
创建新窗口相对简单,您可以选择为窗口提供标题

final Window window = new BasicWindow("My Root Window");
Window最初没有内容,您需要调用以setComponent(..)填充它。在这种情况下,实际上通常情况下,您会想要使用多个组件,因此我们将创建一个 Panel可以容纳多个子组件的复合组件。这是我们决定布局管理器应该是什么的地方。

Panel contentPanel = new Panel(new GridLayout(2));
Lanterna 包含许多内置的布局管理器,最简单的一个是LinearLayout将组件简单地排列在水平线或垂直线中。在本教程中,我们将使用GridLayout 基于 SWT 中同名的布局管理器。在上面的构造函数中,我们已经指定我们想要一个包含两列的网格,下面我们通过在列之间添加一些间距来进一步自定义布局。

GridLayout gridLayout = (GridLayout)contentPanel.getLayoutManager();
gridLayout.setHorizontalSpacing(3);
最基本的组件之一是Label,它只是显示静态文本。在下面的示例中,我们使用附加到每个组件的布局数据字段为布局管理器提供有关应如何放置的额外提示。显然,布局数据必须从与容器使用的布局管理器相同的布局管理器中创建,否则将被忽略。

Label title = new Label("This is a label that spans two columns");
title.setLayoutData(GridLayout.createLayoutData(
        GridLayout.Alignment.BEGINNING, // Horizontal alignment in the grid cell if the cell is larger than the component's preferred size
        GridLayout.Alignment.BEGINNING, // Vertical alignment in the grid cell if the cell is larger than the component's preferred size
        true,       // Give the component extra horizontal space if available
        false,        // Give the component extra vertical space if available
        2,                  // Horizontal span
        1));                  // Vertical span
contentPanel.addComponent(title);
由于网格有两列,我们可以在不需要进一步自定义组件时执行类似的操作来添加组件。

contentPanel.addComponent(new Label("Text Box (aligned)"));
contentPanel.addComponent(
        new TextBox()
            .setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.BEGINNING, GridLayout.Alignment.CENTER)));
这是一个自定义常规TextBox组件的示例,它可以屏蔽内容并可以用于密码输入。

contentPanel.addComponent(new Label("Password Box (right aligned)"));
contentPanel.addComponent(
        new TextBox()
            .setMask('*')
            .setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER)));
虽然我们不打算在这里演示所有组件,但这里有一个ComboBoxes 的示例,一个是只读的,一个是可编辑的。

contentPanel.addComponent(new Label("Read-only Combo Box (forced size)"));
List<String> timezonesAsStrings = new ArrayList<String>();
for(String id: TimeZone.getAvailableIDs()) {
    timezonesAsStrings.add(id);
}
ComboBox<String> readOnlyComboBox = new ComboBox<String>(timezonesAsStrings);
readOnlyComboBox.setReadOnly(true);
readOnlyComboBox.setPreferredSize(new TerminalSize(20, 1));
contentPanel.addComponent(readOnlyComboBox);

contentPanel.addComponent(new Label("Editable Combo Box (filled)"));
contentPanel.addComponent(
        new ComboBox<String>("Item #1", "Item #2", "Item #3", "Item #4")
                .setReadOnly(false)
                .setLayoutData(GridLayout.createHorizontallyFilledLayoutData(1)));
一些用户可交互对象,如Buttons,通过注册回调方法来工作。在此示例中,我们在触发按钮时使用预定义的对话框之一。

contentPanel.addComponent(new Label("Button (centered)"));
contentPanel.addComponent(new Button("Button", new Runnable() {
    @Override
    public void run() {
        MessageDialog.showMessageDialog(textGUI, "MessageBox", "This is a message box", MessageDialogButton.OK);
    }
}).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER)));
用一个空行和一个分隔符关闭,然后用一个按钮关闭窗口

contentPanel.addComponent(
        new EmptySpace()
                .setLayoutData(
                        GridLayout.createHorizontallyFilledLayoutData(2)));
contentPanel.addComponent(
        new Separator(Direction.HORIZONTAL)
                .setLayoutData(
                        GridLayout.createHorizontallyFilledLayoutData(2)));
contentPanel.addComponent(
        new Button("Close", new Runnable() {
            @Override
            public void run() {
                window.close();
            }
        }).setLayoutData(
                GridLayout.createHorizontallyEndAlignedLayoutData(2)));
现在,我们的内容面板已完全填充了组件。一个常见的错误是忘记将它附加到窗口上,所以让我们确保这样做。

window.setComponent(contentPanel);
现在Window已创建并完全填充。正如上面关于线程模型的讨论,我们可以选择在这里启动 GUI,然后再决定何时停止它。为了使其工作,您需要一个专用的 UI 线程来运行所有 GUI 操作,通常通过在 SeparateTextGUIThread创建TextGUI. 在本教程中,我们使用概念上更简单的SameTextGUIThread,它本质上劫持调用者线程并将其用作 GUI 线程,直到满足某些停止条件。最简单的方法是简单地让 lanterna 显示窗口并等待它关闭。这将启动事件循环并使 GUI 正常工作。在上面的“关闭”按钮中,我们将调用绑定到close()当按钮被触发时,Window 对象上的方法,这将打破偶数循环,我们的调用最终返回。

textGUI.addWindowAndWait(window);
当我们的调用返回时,窗口关闭并且不再可见。屏幕仍然包含TextGUI它离开的最后一个状态,因此我们可以轻松地添加和显示另一个窗口,而不会出现任何闪烁。在这种情况下,我们想关闭整个事物并返回普通提示。我们只需要为此停止底层Screen,TextGUI系统不需要任何额外的反汇编。

}
catch (IOException e) {
    e.printStackTrace();
}
finally {
    if(screen != null) {
        try {
            screen.stopScreen();
        }
        catch(IOException e) {
            e.printStackTrace();
        }
    }
}    

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值