Sciter.js 指南 - 自定义GUI程序头部-微信聊天窗

这个文档会教你怎么样自定义一个自己样式的窗口,不要长得和操作系统原生的样。这时有定制样式,功能,比如关闭,最大化,最小化。还有点击什么位置能让程序拖动。

为什么需要自定义窗口头部?

默认情况下,操作系统会为应用程序窗口提供标准的标题栏。但在许多情况下,您可能希望:

  • 品牌化: 让窗口外观与您的应用程序设计保持一致。
  • 添加自定义控件: 在标题栏中加入搜索框、工具栏按钮或其他 UI 元素。
  • 独特的视觉风格: 创建一个完全自定义的、非标准的窗口外观。

Sciter.js 提供了强大的机制,让您可以使用 HTML 和 CSS 完全控制窗口的外观和行为。

核心概念:window-frame="extended"

要开始自定义窗口头部,最关键的一步是在您主 HTML 文件的根 <html> 元素上设置 window-frame="extended" 属性。这告诉 Sciter 您将自己绘制窗口的非客户区(包括标题栏和边框)。

html 复制代码
<html window-frame="extended" window-width="800px" window-height="600px">
  <head>
    <title>我的自定义窗口</title>
    <style src="window.css" />
    <script type="module" src="main.js" />
  </head>
  <body>
    <!-- 窗口内容 -->
  </body>
</html>

使用 HTML 构建头部结构

设置 window-frame="extended" 后,您需要使用 HTML 元素来定义窗口头部的结构。通常,这会放在 <body> 标签之前,或者作为 <body> 的第一个子元素(取决于您的布局)。一个常见的结构是使用 <header> 元素。

关键在于使用特定的 role 属性来告诉 Sciter 哪些元素扮演标准窗口控件的角色:

  • role="window-caption": 必需。指定一个元素作为窗口标题栏。用户可以通过拖动此元素来移动窗口。

  • role="window-icon": (可选)指定一个元素作为窗口图标。在 Windows 上,单击此图标通常会显示窗口菜单。

  • role="window-minimize": (可选)使元素表现为最小化按钮。

  • role="window-maximize": (可选)使元素表现为最大化/还原按钮。

  • role="window-close": (可选)使元素表现为关闭按钮。

    重要提示 : 当用户点击带有 role="window-minimize", role="window-maximize", 或 role="window-close" 属性的元素时,Sciter 会自动处理相应的窗口操作(最小化、最大化/还原、关闭),您通常不需要为此编写额外的 JavaScript 事件处理代码。

示例 HTML 结构:

html 复制代码
<header>
  <img role="window-icon" src="images/my-icon.svg" />
  <window-caption role="window-caption">我的应用程序标题</window-caption>
  <window-buttons>
    <window-button role="window-minimize"></window-button>
    <window-button role="window-maximize"></window-button>
    <window-button role="window-close"></window-button>
  </window-buttons>
</header>

在这个例子中:

  • 我们使用 <header> 作为容器。
  • 一个 <img> 标签用作窗口图标。
  • 一个 <window-caption> (或任何其他元素,如 <span>, <div>)用作可拖动的标题区域。
  • 一个 <window-buttons> 容器(通常是 <div> 或自定义元素)包含标准的窗口控制按钮。
  • <window-button> (通常是 <button> 或自定义元素)扮演最小化、最大化和关闭的角色。 注意这些也可以直接写进 css 的类当中,也是相当的方便。

使用 CSS 设计头部样式

一旦有了 HTML 结构,您就可以使用 CSS 来完全控制头部的外观。

关键 CSS 属性和技术:

  • 布局 (flow: horizontal) : 通常,头部元素(图标、标题、按钮)是水平排列的。flow: horizontal; 是实现这一点的常用方法。
  • 高度 (height: window-caption-height) : Sciter 提供了一个 CSS 变量 window-caption-height,它代表了操作系统标准标题栏的高度。使用它可以让您的自定义头部具有合适的高度。
  • 宽度 (width: *) : 对于像标题这样需要填充可用空间的元素,width: *; (星号宽度) 非常有用。
  • 背景和颜色 : 使用 background-color, color 等标准 CSS 属性来设置背景和文本颜色。
  • 按钮样式 :
    • 大小 : width: window-button-width; 可以用来获取标准按钮宽度。
    • 图标 : 使用 foreground-image (对于 Sciter 的矢量图标路径) 或 background-image 来设置按钮图标。
    • 描边/填充 (stroke, fill) : 对于矢量图标,使用 strokefill 来控制颜色。
    • 悬停效果 (:hover): 定义鼠标悬停在按钮上时的样式变化(例如,改变背景色)。
  • 平台特定样式 (@media platform) : 您可以使用 @media 查询来为不同操作系统(如 Windows, Linux, OSX)应用不同的样式。

使用 JavaScript 自定义行为 (可选)

虽然 Sciter 会自动处理带有 role="window-..." 属性的元素的默认行为(最小化、最大化、关闭),但在某些高级场景下,您可能希望通过 JavaScript 完全控制这些按钮的点击事件。例如,在关闭窗口前显示一个确认对话框。

为此,您可以:

  1. 不使用 role 属性 : 如果您想完全接管,可以不在按钮上设置 role="window-close" 等属性。
  2. 添加事件监听器 : 使用 JavaScript 为您的按钮元素(例如,通过 ID 或 class 选择)添加 click 事件监听器。
  3. 调用窗口 API: 在事件处理函数中,调用相应的 Sciter 窗口 API 来执行所需的操作。

示例:自定义关闭按钮

假设您的关闭按钮 HTML 是:

html 复制代码
<button id="my-close-button">关闭</button>

您的 sciter.js 中的 JS 代码可以这样写:

javascript 复制代码
on["click", "button#my-close-button"], (evt) {
    // 阻止可能的默认行为(如果设置了 role)
    // event.stopPropagation(); // 通常不需要,除非 role 存在且引起冲突

    // 显示确认对话框
    const confirmed = Window.this.modal(<alert caption="确认">确定要关闭窗口吗?</alert>);

    if (confirmed) {
        Window.this.close(); // 调用关闭窗口 API
    }
});

类似地,您可以使用以下 API 来自定义其他按钮:

  • 最小化 : Window.this.state = Window.WINDOW_MINIMIZED;
  • 最大化/还原 : 通过检查当前状态 Window.this.state (例如 Window.WINDOW_MAXIMIZED) 来决定是最大化 (Window.this.state = Window.WINDOW_MAXIMIZED;) 还是还原 (Window.this.state = Window.WINDOW_RESTORED;)。

注意: 如果您选择使用 JavaScript 处理事件,请确保您的逻辑覆盖了所有必要的窗口状态管理。

完整示例

这是使用 sciter.js 写的微信一样的头部。

下面是一个将前面讨论的概念整合在一起的微信风格的自定义 GUI 的软件头部的完整示例。

html 复制代码
<!DOCTYPE html>
<html window-frame="extended" window-width="800px" window-height="600px"  window-corners="not-round" >

<head>
    <meta charset="UTF-8">
    <title>微信</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: "Microsoft YaHei", sans-serif;
        }

        html {
            border-radius: 0px;
        }

        body {
            background-color: #f5f5f5;
        }

        .container {
            flow: horizontal;
            height: *;
        }

        .sidebar {
            width: 60px;
            background-color: #2c2c2c;
            height: 100%;
            flow: vertical;
            content-horizontal-align: center;
            padding-top: 20px;
        }

        .avatar {
            width: 50px;
            height: 50px;
            border-radius: 4px;
            overflow: hidden;
            background-color: #555;
            text-align: center;
            line-height: 50px;
            font-size: 35px;
        }

        .main {
            width: 1*;
            flow: vertical;
        }

        .header {
            height: 56px;
            background-color: #f6f6f6;
            border-bottom: 1px solid #e0e0e0;
            flow: horizontal;
            padding: 0 10px;
            position: relative;
        }

        .search-area {
            flow: horizontal;
            vertical-align: middle;
            width: 340px;
        }

        .search-container {
            flow: horizontal;
            align-items: center;
            background-color: #e6e6e6;
            border-radius: 4px;
            padding: 4px 10px;
            width: 300px;
            height: 24px;
        }

        .search-icon {
            color: #999;
            margin-right: 8px;
            font-size: 16px;
        }

        .search-input {
            background: none;
            border: none;
            outline: none;
            width: 1*;
            color: #999;
            font-size: 12px;
        }

        .add-button {
            width: 24px;
            height: 24px;
            border-radius: 4px;
            background-color: #e6e6e6;
            border: none;
            margin-left: 10px;
            font-size: 16px;
            cursor: pointer;
            text-align: center;
            line-height: 24px;
            color: #666;
        }

        .divider {
            width: 0.5px;
            height: 100%;
            background-color: #e0e0e0;
            margin: 0 20px;
        }

        .user-info {
            color: #333;
            font-weight: bold;
            font-size: 16px;
            width: 1*;
            padding-left: 20px;
            line-height: 56px;
            min-width: 200px;
            role: window-caption;
            /* 按这块就可以拖动 */
        }

        .controls-container {
            flow: vertical;
        }

        .window-controls {
            margin-top: 8px;
            flow: horizontal;
        }

        .control-button {
            background: none;
            border: none;
            cursor: pointer;
            size: 16px;
            margin-left: 15px;
            margin-right: 5px;
            stroke: #666;
        }

        .control-button:hover {
            stroke: #333;
        }

        .control-button:nth-child(1) {
            role: window-minimize;
            foreground-image: url(icon:window-minimize);
        }

        .control-button:nth-child(2) {
            role: window-maximize;
            foreground-image: url(icon:window-maximize);
        }

        .control-button:nth-child(3) {
            role: window-close;
            foreground-image: url(icon:window-close);
        }

        .control-button:nth-child(3):hover {
            stroke: #ff0000;
        }

        .more-button-container {
            margin-top: 4px;
            margin-right: 10px;
            text-align: right;
        }

        .more-button {
            background: none;
            border: none;
            cursor: pointer;
            size: 16px;
            stroke: #666;
            foreground-image: url(icon:opts-h);
            foreground-size: 18px;
            margin-left: *;
        }

        .more-button:hover {
            stroke: #333;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="sidebar">
            <div class="avatar">
                👨
            </div>
        </div>
        <div class="main">
            <div class="header">
                <div class="search-area">
                    <div class="search-container">
                        <span class="search-icon">🔍</span>
                        <input type="text" class="search-input" placeholder="搜索">
                    </div>
                    <button class="add-button">+</button>
                </div>
                <div class="divider"></div>
                <div class="user-info">凯哥1970</div>
                <div class="controls-container">
                    <div class="window-controls">
                        <button class="control-button"></button>
                        <button class="control-button"></button>
                        <button class="control-button"></button>
                    </div>
                    <div class="more-button-container">
                        <button class="more-button"></button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>

</html>

最佳实践

  1. 保持简洁: 除非绝对必要,否则不要在标题栏中添加过多的交互元素,以免让用户混淆。
  2. 明确可拖动区域 : 确保 role="window-caption" 的元素足够大且易于点击和拖动。
  3. 提供标准控件 : 除非您的 UI 设计有充分理由省略它们,否则应始终提供最小化、最大化和关闭按钮,并赋予它们正确的 role
  4. 使用 CSS 变量 : 利用 Sciter 提供的变量(如 window-caption-height, window-button-width, window-accent-color)来适应系统设置。
  5. 分离关注点: 将 HTML 结构、CSS 样式和 JavaScript 行为分别放在不同的文件中,以提高可维护性。
  6. 测试: 在目标平台上测试您的自定义头部,确保外观和行为符合预期。

总结

通过在 <html> 标签上设置 window-frame="extended",并结合使用带有特定 role 属性的 HTML 元素和 CSS 样式,您可以完全控制 Sciter.js 应用程序窗口的外观。这为您提供了创建美观、品牌化和功能丰富的用户界面的能力。

查阅 Sciter SDK 中的示例(特别是 samples.sciter/window/chrome-types/demos/usciter/ 目录下的示例)以获取更多灵感和实现细节。

相关推荐
小小小小宇11 分钟前
PC和WebView白屏检测
前端
天天扭码23 分钟前
ES6 Symbol 超详细教程:为什么它是避免对象属性冲突的终极方案?
前端·javascript·面试
小矮马26 分钟前
React-组件和props
前端·javascript·react.js
懒羊羊我小弟30 分钟前
React Router v7 从入门到精通指南
前端·react.js·前端框架
DC...1 小时前
vue滑块组件设计与实现
前端·javascript·vue.js
Mars狐狸1 小时前
AI项目改用服务端组件实现对话?包体积减小50%!
前端·react.js
H5开发新纪元1 小时前
Vite 项目打包分析完整指南:从配置到优化
前端·vue.js
嘻嘻嘻嘻嘻嘻ys1 小时前
《Vue 3.3响应式革新与TypeScript高效开发实战指南》
前端·后端
恋猫de小郭2 小时前
腾讯 Kuikly 正式开源,了解一下这个基于 Kotlin 的全平台框架
android·前端·ios
2301_799404912 小时前
如何修改npm的全局安装路径?
前端·npm·node.js