4.1 事件系统概述

Sciter事件模型

Sciter采用了类似于Web浏览器的事件模型,但针对桌面应用进行了优化和扩展。事件系统是用户界面交互的核心,它允许应用程序响应用户操作、系统消息和自定义事件。

事件流程

  1. 事件产生:用户操作或系统触发
  2. 事件捕获:从根元素向目标元素传播
  3. 事件处理:在目标元素上执行处理函数
  4. 事件冒泡:从目标元素向根元素传播
  5. 默认行为:执行元素的默认行为(可阻止)

事件类型

  • 鼠标事件:点击、移动、滚轮等
  • 键盘事件:按键按下、释放等
  • 焦点事件:获得焦点、失去焦点
  • 表单事件:输入、提交、重置等
  • 窗口事件:调整大小、关闭等
  • 自定义事件:应用程序定义的事件

4.2 DOM事件处理

事件绑定方法

1. HTML内联事件处理

<!-- 直接在HTML中定义事件处理 -->
<button onclick="handleClick(this)">点击我</button>
<input onchange="handleInputChange(this)" />
<div onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">
    悬停显示提示
</div>

<script type="text/tiscript">
function handleClick(element) {
    stdout.println("按钮被点击: " + element.innerText);
}

function handleInputChange(element) {
    stdout.println("输入值改变: " + element.value);
}

function showTooltip(element) {
    element.attributes["title"] = "这是一个提示";
}

function hideTooltip(element) {
    element.attributes.removeNamedItem("title");
}
</script>

2. 脚本事件绑定

// 使用on方法绑定事件
function self.ready() {
    // 单个元素事件绑定
    var button = $("#my-button");
    button.on("click", function(evt) {
        stdout.println("按钮被点击");
        stdout.println("事件类型: " + evt.type);
        stdout.println("目标元素: " + evt.target.tag);
    });
    
    // 多个事件绑定
    var input = $("#my-input");
    input.on("focus blur", function(evt) {
        if (evt.type == "focus") {
            this.addClass("focused");
        } else {
            this.removeClass("focused");
        }
    });
    
    // 事件委托(为动态元素绑定事件)
    $("#container").on("click", "button", function(evt) {
        stdout.println("容器内的按钮被点击: " + this.innerText);
    });
    
    // 一次性事件
    $("#one-time-button").on("click.once", function(evt) {
        stdout.println("这个事件只会触发一次");
    });
}

// 移除事件监听
function removeEventListeners() {
    $("#my-button").off("click");
    $("#my-input").off(); // 移除所有事件
}

3. 事件处理函数详解

// 完整的事件处理函数
function handleComplexEvent(evt) {
    // 事件基本信息
    stdout.println("事件类型: " + evt.type);
    stdout.println("目标元素: " + evt.target.tag);
    stdout.println("当前元素: " + evt.currentTarget.tag);
    stdout.println("时间戳: " + evt.timeStamp);
    
    // 鼠标事件信息
    if (evt.type.indexOf("mouse") >= 0) {
        stdout.println("鼠标位置: (" + evt.clientX + ", " + evt.clientY + ")");
        stdout.println("屏幕位置: (" + evt.screenX + ", " + evt.screenY + ")");
        stdout.println("按键状态: Ctrl=" + evt.ctrlKey + ", Shift=" + evt.shiftKey + ", Alt=" + evt.altKey);
        stdout.println("鼠标按键: " + evt.button);
    }
    
    // 键盘事件信息
    if (evt.type.indexOf("key") >= 0) {
        stdout.println("按键代码: " + evt.keyCode);
        stdout.println("字符代码: " + evt.charCode);
        stdout.println("按键: " + evt.key);
        stdout.println("修饰键: Ctrl=" + evt.ctrlKey + ", Shift=" + evt.shiftKey + ", Alt=" + evt.altKey);
    }
    
    // 阻止默认行为
    if (evt.type == "contextmenu") {
        evt.preventDefault();
        stdout.println("阻止了右键菜单");
    }
    
    // 停止事件传播
    if (evt.target.hasClass("stop-propagation")) {
        evt.stopPropagation();
        stdout.println("停止事件冒泡");
    }
}

常用事件类型

1. 鼠标事件

// 鼠标事件处理
function setupMouseEvents() {
    var element = $("#interactive-area");
    
    // 鼠标点击事件
    element.on("click", function(evt) {
        stdout.println("单击: 按键=" + evt.button);
    });
    
    element.on("dblclick", function(evt) {
        stdout.println("双击");
    });
    
    element.on("contextmenu", function(evt) {
        stdout.println("右键菜单");
        // 显示自定义菜单
        showCustomMenu(evt.clientX, evt.clientY);
        evt.preventDefault();
    });
    
    // 鼠标按下和释放
    element.on("mousedown", function(evt) {
        this.addClass("pressed");
        stdout.println("鼠标按下");
    });
    
    element.on("mouseup", function(evt) {
        this.removeClass("pressed");
        stdout.println("鼠标释放");
    });
    
    // 鼠标移动事件
    element.on("mousemove", function(evt) {
        // 节流处理,避免过多事件
        if (!this._lastMoveTime || Date.now() - this._lastMoveTime > 100) {
            this._lastMoveTime = Date.now();
            updateMousePosition(evt.clientX, evt.clientY);
        }
    });
    
    // 鼠标进入和离开
    element.on("mouseenter", function(evt) {
        this.addClass("hover");
        showTooltip(this);
    });
    
    element.on("mouseleave", function(evt) {
        this.removeClass("hover");
        hideTooltip();
    });
    
    // 鼠标滚轮事件
    element.on("wheel", function(evt) {
        var delta = evt.deltaY;
        if (delta > 0) {
            stdout.println("向下滚动");
        } else {
            stdout.println("向上滚动");
        }
        
        // 阻止页面滚动
        evt.preventDefault();
    });
}

// 自定义菜单实现
function showCustomMenu(x, y) {
    var menu = $("#context-menu");
    if (!menu) {
        menu = Element.create("div", {
            id: "context-menu",
            class: "context-menu",
            html: `
                <div class="menu-item" data-action="copy">复制</div>
                <div class="menu-item" data-action="paste">粘贴</div>
                <div class="menu-separator"></div>
                <div class="menu-item" data-action="delete">删除</div>
            `
        });
        document.body.append(menu);
        
        // 绑定菜单项事件
        menu.on("click", ".menu-item", function(evt) {
            var action = this.attributes["data-action"];
            executeMenuAction(action);
            hideCustomMenu();
        });
    }
    
    menu.style.left = x + "px";
    menu.style.top = y + "px";
    menu.style.display = "block";
    
    // 点击其他地方隐藏菜单
    document.on("click.menu", function(evt) {
        if (!menu.contains(evt.target)) {
            hideCustomMenu();
        }
    });
}

function hideCustomMenu() {
    var menu = $("#context-menu");
    if (menu) {
        menu.style.display = "none";
        document.off("click.menu");
    }
}

function executeMenuAction(action) {
    switch (action) {
        case "copy":
            stdout.println("执行复制操作");
            break;
        case "paste":
            stdout.println("执行粘贴操作");
            break;
        case "delete":
            stdout.println("执行删除操作");
            break;
    }
}

2. 键盘事件

// 键盘事件处理
function setupKeyboardEvents() {
    // 全局键盘事件
    document.on("keydown", function(evt) {
        handleGlobalKeydown(evt);
    });
    
    // 输入框键盘事件
    $("#text-input").on("keydown keyup keypress", function(evt) {
        handleInputKeyboard(evt);
    });
}

function handleGlobalKeydown(evt) {
    // 快捷键处理
    if (evt.ctrlKey) {
        switch (evt.keyCode) {
            case 83: // Ctrl+S
                evt.preventDefault();
                saveDocument();
                break;
            case 79: // Ctrl+O
                evt.preventDefault();
                openDocument();
                break;
            case 78: // Ctrl+N
                evt.preventDefault();
                newDocument();
                break;
            case 90: // Ctrl+Z
                evt.preventDefault();
                undo();
                break;
            case 89: // Ctrl+Y
                evt.preventDefault();
                redo();
                break;
        }
    }
    
    // 功能键处理
    switch (evt.keyCode) {
        case 112: // F1
            evt.preventDefault();
            showHelp();
            break;
        case 123: // F12
            evt.preventDefault();
            toggleDevTools();
            break;
        case 27: // Escape
            closeModal();
            break;
    }
}

function handleInputKeyboard(evt) {
    var input = evt.target;
    
    if (evt.type == "keydown") {
        // 特殊键处理
        switch (evt.keyCode) {
            case 13: // Enter
                if (evt.ctrlKey) {
                    // Ctrl+Enter 提交
                    submitForm();
                } else {
                    // 普通Enter,插入换行(如果是textarea)
                    if (input.tag == "textarea") {
                        insertText(input, "\n");
                        evt.preventDefault();
                    }
                }
                break;
            case 9: // Tab
                if (evt.shiftKey) {
                    // Shift+Tab 减少缩进
                    decreaseIndent(input);
                } else {
                    // Tab 增加缩进
                    increaseIndent(input);
                }
                evt.preventDefault();
                break;
        }
    }
    
    if (evt.type == "keyup") {
        // 输入验证
        validateInput(input);
        
        // 自动保存
        scheduleAutoSave();
    }
}

// 文本操作辅助函数
function insertText(input, text) {
    var start = input.selectionStart;
    var end = input.selectionEnd;
    var value = input.value;
    
    input.value = value.substring(0, start) + text + value.substring(end);
    input.selectionStart = input.selectionEnd = start + text.length;
}

function increaseIndent(input) {
    var start = input.selectionStart;
    var end = input.selectionEnd;
    var value = input.value;
    var lines = value.split("\n");
    
    // 找到选中的行
    var startLine = value.substring(0, start).split("\n").length - 1;
    var endLine = value.substring(0, end).split("\n").length - 1;
    
    // 为每行添加缩进
    for (var i = startLine; i <= endLine; i++) {
        lines[i] = "    " + lines[i];
    }
    
    input.value = lines.join("\n");
}

function decreaseIndent(input) {
    var start = input.selectionStart;
    var end = input.selectionEnd;
    var value = input.value;
    var lines = value.split("\n");
    
    var startLine = value.substring(0, start).split("\n").length - 1;
    var endLine = value.substring(0, end).split("\n").length - 1;
    
    // 移除每行的缩进
    for (var i = startLine; i <= endLine; i++) {
        if (lines[i].startsWith("    ")) {
            lines[i] = lines[i].substring(4);
        } else if (lines[i].startsWith("\t")) {
            lines[i] = lines[i].substring(1);
        }
    }
    
    input.value = lines.join("\n");
}

3. 表单事件

// 表单事件处理
function setupFormEvents() {
    var form = $("#user-form");
    
    // 表单提交事件
    form.on("submit", function(evt) {
        evt.preventDefault(); // 阻止默认提交
        
        if (validateForm(this)) {
            submitFormData(this);
        } else {
            showValidationErrors();
        }
    });
    
    // 表单重置事件
    form.on("reset", function(evt) {
        clearValidationErrors();
        resetFormState();
    });
    
    // 输入字段事件
    form.on("input", "input, textarea, select", function(evt) {
        handleFieldInput(this);
    });
    
    form.on("change", "input, textarea, select", function(evt) {
        handleFieldChange(this);
    });
    
    form.on("focus", "input, textarea, select", function(evt) {
        handleFieldFocus(this);
    });
    
    form.on("blur", "input, textarea, select", function(evt) {
        handleFieldBlur(this);
    });
}

function handleFieldInput(field) {
    // 实时输入处理
    clearFieldError(field);
    
    // 字符计数
    if (field.hasAttribute("maxlength")) {
        updateCharacterCount(field);
    }
    
    // 实时验证
    if (field.hasAttribute("data-validate-realtime")) {
        validateField(field);
    }
}

function handleFieldChange(field) {
    // 值改变处理
    validateField(field);
    updateFormState();
    
    // 特殊字段处理
    if (field.name == "country") {
        updateStateOptions(field.value);
    } else if (field.name == "userType") {
        toggleUserTypeFields(field.value);
    }
}

function handleFieldFocus(field) {
    // 获得焦点处理
    field.addClass("focused");
    showFieldHelp(field);
}

function handleFieldBlur(field) {
    // 失去焦点处理
    field.removeClass("focused");
    hideFieldHelp(field);
    validateField(field);
}

// 表单验证
function validateForm(form) {
    var isValid = true;
    var fields = form.$$(".required");
    
    for (var field of fields) {
        if (!validateField(field)) {
            isValid = false;
        }
    }
    
    return isValid;
}

function validateField(field) {
    var value = field.value.trim();
    var isValid = true;
    var errorMessage = "";
    
    // 必填验证
    if (field.hasClass("required") && !value) {
        isValid = false;
        errorMessage = "此字段为必填项";
    }
    
    // 类型验证
    if (isValid && value) {
        var type = field.getAttribute("data-type");
        switch (type) {
            case "email":
                if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
                    isValid = false;
                    errorMessage = "请输入有效的邮箱地址";
                }
                break;
            case "phone":
                if (!/^\d{11}$/.test(value)) {
                    isValid = false;
                    errorMessage = "请输入11位手机号码";
                }
                break;
            case "number":
                if (isNaN(value)) {
                    isValid = false;
                    errorMessage = "请输入有效的数字";
                }
                break;
        }
    }
    
    // 长度验证
    if (isValid && value) {
        var minLength = field.getAttribute("minlength");
        var maxLength = field.getAttribute("maxlength");
        
        if (minLength && value.length < parseInt(minLength)) {
            isValid = false;
            errorMessage = `最少需要${minLength}个字符`;
        }
        
        if (maxLength && value.length > parseInt(maxLength)) {
            isValid = false;
            errorMessage = `最多允许${maxLength}个字符`;
        }
    }
    
    // 显示验证结果
    if (isValid) {
        field.removeClass("error");
        field.addClass("valid");
        clearFieldError(field);
    } else {
        field.removeClass("valid");
        field.addClass("error");
        showFieldError(field, errorMessage);
    }
    
    return isValid;
}

function showFieldError(field, message) {
    var errorElement = field.parent.$("."+field.name+"-error");
    if (!errorElement) {
        errorElement = Element.create("div", {
            class: field.name + "-error field-error",
            text: message
        });
        field.parent.append(errorElement);
    } else {
        errorElement.innerText = message;
    }
}

function clearFieldError(field) {
    var errorElement = field.parent.$("."+field.name+"-error");
    if (errorElement) {
        errorElement.remove();
    }
}

4.3 自定义事件

创建和触发自定义事件

1. 基本自定义事件

// 创建自定义事件类
class CustomEvent {
    function this(type, data = {}) {
        this.type = type;
        this.data = data;
        this.timestamp = Date.now();
        this.defaultPrevented = false;
        this.propagationStopped = false;
    }
    
    function preventDefault() {
        this.defaultPrevented = true;
    }
    
    function stopPropagation() {
        this.propagationStopped = true;
    }
}

// 事件发射器类
class EventEmitter {
    function this() {
        this._events = {};
    }
    
    // 注册事件监听器
    function on(eventType, handler) {
        if (!this._events[eventType]) {
            this._events[eventType] = [];
        }
        this._events[eventType].push(handler);
        return this;
    }
    
    // 注册一次性事件监听器
    function once(eventType, handler) {
        var self = this;
        function onceHandler(evt) {
            handler.call(this, evt);
            self.off(eventType, onceHandler);
        }
        return this.on(eventType, onceHandler);
    }
    
    // 移除事件监听器
    function off(eventType, handler) {
        if (!this._events[eventType]) return this;
        
        if (!handler) {
            // 移除所有监听器
            delete this._events[eventType];
        } else {
            // 移除特定监听器
            var index = this._events[eventType].indexOf(handler);
            if (index >= 0) {
                this._events[eventType].splice(index, 1);
            }
        }
        return this;
    }
    
    // 触发事件
    function emit(eventType, data) {
        if (!this._events[eventType]) return this;
        
        var event = new CustomEvent(eventType, data);
        var handlers = this._events[eventType].slice(); // 复制数组
        
        for (var handler of handlers) {
            try {
                handler.call(this, event);
                if (event.propagationStopped) break;
            } catch (error) {
                stderr.println("事件处理器错误: " + error.message);
            }
        }
        
        return this;
    }
    
    // 获取监听器数量
    function listenerCount(eventType) {
        return this._events[eventType] ? this._events[eventType].length : 0;
    }
    
    // 获取所有事件类型
    function eventNames() {
        return Object.keys(this._events);
    }
}

// 使用示例
var emitter = new EventEmitter();

// 注册事件监听器
emitter.on("user-login", function(evt) {
    stdout.println("用户登录: " + evt.data.username);
    updateUserInterface(evt.data);
});

emitter.on("user-logout", function(evt) {
    stdout.println("用户登出");
    clearUserInterface();
});

emitter.on("data-updated", function(evt) {
    stdout.println("数据更新: " + evt.data.table);
    refreshDataView(evt.data);
});

// 触发事件
emitter.emit("user-login", {
    username: "张三",
    userId: 123,
    role: "admin"
});

emitter.emit("data-updated", {
    table: "users",
    action: "insert",
    recordId: 456
});

2. 应用级事件系统

// 全局应用事件系统
namespace AppEvents {
    var _emitter = new EventEmitter();
    
    // 预定义事件类型
    const EVENT_TYPES = {
        APP_READY: "app-ready",
        USER_LOGIN: "user-login",
        USER_LOGOUT: "user-logout",
        DATA_CHANGED: "data-changed",
        SETTINGS_UPDATED: "settings-updated",
        ERROR_OCCURRED: "error-occurred",
        NOTIFICATION_SHOW: "notification-show",
        WINDOW_RESIZE: "window-resize",
        THEME_CHANGED: "theme-changed"
    };
    
    // 公共接口
    function on(eventType, handler) {
        return _emitter.on(eventType, handler);
    }
    
    function once(eventType, handler) {
        return _emitter.once(eventType, handler);
    }
    
    function off(eventType, handler) {
        return _emitter.off(eventType, handler);
    }
    
    function emit(eventType, data) {
        stdout.println(`[AppEvents] 触发事件: ${eventType}`);
        return _emitter.emit(eventType, data);
    }
    
    // 便捷方法
    function notifyUserLogin(user) {
        return emit(EVENT_TYPES.USER_LOGIN, user);
    }
    
    function notifyUserLogout() {
        return emit(EVENT_TYPES.USER_LOGOUT);
    }
    
    function notifyDataChanged(table, action, data) {
        return emit(EVENT_TYPES.DATA_CHANGED, {
            table: table,
            action: action,
            data: data,
            timestamp: Date.now()
        });
    }
    
    function notifyError(error, context) {
        return emit(EVENT_TYPES.ERROR_OCCURRED, {
            error: error,
            context: context,
            timestamp: Date.now()
        });
    }
    
    function showNotification(message, type = "info") {
        return emit(EVENT_TYPES.NOTIFICATION_SHOW, {
            message: message,
            type: type,
            timestamp: Date.now()
        });
    }
    
    // 导出常量和函数
    self.EVENT_TYPES = EVENT_TYPES;
    self.on = on;
    self.once = once;
    self.off = off;
    self.emit = emit;
    self.notifyUserLogin = notifyUserLogin;
    self.notifyUserLogout = notifyUserLogout;
    self.notifyDataChanged = notifyDataChanged;
    self.notifyError = notifyError;
    self.showNotification = showNotification;
}

// 使用应用事件系统
function setupApplicationEvents() {
    // 监听用户登录事件
    AppEvents.on(AppEvents.EVENT_TYPES.USER_LOGIN, function(evt) {
        var user = evt.data;
        stdout.println(`欢迎 ${user.name}!`);
        
        // 更新UI
        updateUserMenu(user);
        loadUserPreferences(user.id);
        
        // 记录日志
        logUserActivity("login", user.id);
    });
    
    // 监听数据变化事件
    AppEvents.on(AppEvents.EVENT_TYPES.DATA_CHANGED, function(evt) {
        var data = evt.data;
        stdout.println(`数据表 ${data.table} 发生 ${data.action} 操作`);
        
        // 刷新相关视图
        refreshViews(data.table);
        
        // 标记数据为脏状态
        markDataDirty(data.table);
    });
    
    // 监听错误事件
    AppEvents.on(AppEvents.EVENT_TYPES.ERROR_OCCURRED, function(evt) {
        var error = evt.data.error;
        var context = evt.data.context;
        
        stderr.println(`错误发生在 ${context}: ${error.message}`);
        
        // 显示错误通知
        showErrorDialog(error.message);
        
        // 发送错误报告
        sendErrorReport(error, context);
    });
    
    // 监听通知事件
    AppEvents.on(AppEvents.EVENT_TYPES.NOTIFICATION_SHOW, function(evt) {
        var notification = evt.data;
        showToast(notification.message, notification.type);
    });
}

事件总线模式

1. 模块间通信

// 事件总线实现
class EventBus {
    function this() {
        this._channels = {};
        this._middlewares = [];
    }
    
    // 添加中间件
    function use(middleware) {
        this._middlewares.push(middleware);
        return this;
    }
    
    // 订阅频道
    function subscribe(channel, handler, priority = 0) {
        if (!this._channels[channel]) {
            this._channels[channel] = [];
        }
        
        this._channels[channel].push({
            handler: handler,
            priority: priority
        });
        
        // 按优先级排序
        this._channels[channel].sort((a, b) => b.priority - a.priority);
        
        return this;
    }
    
    // 取消订阅
    function unsubscribe(channel, handler) {
        if (!this._channels[channel]) return this;
        
        this._channels[channel] = this._channels[channel].filter(
            item => item.handler !== handler
        );
        
        return this;
    }
    
    // 发布消息
    function publish(channel, message, options = {}) {
        if (!this._channels[channel]) return this;
        
        var event = {
            channel: channel,
            message: message,
            options: options,
            timestamp: Date.now(),
            stopped: false
        };
        
        // 执行中间件
        for (var middleware of this._middlewares) {
            try {
                middleware(event);
                if (event.stopped) return this;
            } catch (error) {
                stderr.println("中间件错误: " + error.message);
            }
        }
        
        // 执行处理器
        var handlers = this._channels[channel].slice();
        for (var item of handlers) {
            try {
                item.handler(event.message, event);
                if (event.stopped) break;
            } catch (error) {
                stderr.println(`频道 ${channel} 处理器错误: ${error.message}`);
            }
        }
        
        return this;
    }
    
    // 清空频道
    function clear(channel) {
        if (channel) {
            delete this._channels[channel];
        } else {
            this._channels = {};
        }
        return this;
    }
    
    // 获取频道信息
    function getChannels() {
        return Object.keys(this._channels);
    }
    
    function getSubscriberCount(channel) {
        return this._channels[channel] ? this._channels[channel].length : 0;
    }
}

// 全局事件总线
var GlobalEventBus = new EventBus();

// 添加日志中间件
GlobalEventBus.use(function(event) {
    stdout.println(`[EventBus] ${event.channel}: ${JSON.stringify(event.message)}`);
});

// 添加权限检查中间件
GlobalEventBus.use(function(event) {
    if (event.options.requireAuth && !isUserAuthenticated()) {
        stderr.println("未授权的事件访问: " + event.channel);
        event.stopped = true;
    }
});

// 模块A:用户管理
namespace UserModule {
    function init() {
        // 订阅用户相关事件
        GlobalEventBus.subscribe("user.create", handleUserCreate);
        GlobalEventBus.subscribe("user.update", handleUserUpdate);
        GlobalEventBus.subscribe("user.delete", handleUserDelete);
    }
    
    function handleUserCreate(userData) {
        stdout.println("创建用户: " + userData.name);
        
        // 创建用户逻辑
        var user = createUser(userData);
        
        // 通知其他模块
        GlobalEventBus.publish("user.created", user);
    }
    
    function handleUserUpdate(userData) {
        stdout.println("更新用户: " + userData.id);
        
        var user = updateUser(userData);
        GlobalEventBus.publish("user.updated", user);
    }
    
    function handleUserDelete(userId) {
        stdout.println("删除用户: " + userId);
        
        deleteUser(userId);
        GlobalEventBus.publish("user.deleted", { id: userId });
    }
    
    self.init = init;
}

// 模块B:通知系统
namespace NotificationModule {
    function init() {
        // 订阅用户事件,发送通知
        GlobalEventBus.subscribe("user.created", function(user) {
            sendWelcomeNotification(user);
        });
        
        GlobalEventBus.subscribe("user.updated", function(user) {
            sendUpdateNotification(user);
        });
        
        // 订阅通知事件
        GlobalEventBus.subscribe("notification.send", handleSendNotification);
    }
    
    function handleSendNotification(notification) {
        stdout.println(`发送通知: ${notification.message}`);
        
        // 发送通知逻辑
        var result = sendNotification(notification);
        
        // 通知发送结果
        GlobalEventBus.publish("notification.sent", {
            notification: notification,
            result: result
        });
    }
    
    function sendWelcomeNotification(user) {
        GlobalEventBus.publish("notification.send", {
            to: user.email,
            subject: "欢迎注册",
            message: `欢迎 ${user.name} 加入我们!`
        });
    }
    
    function sendUpdateNotification(user) {
        GlobalEventBus.publish("notification.send", {
            to: user.email,
            subject: "账户更新",
            message: "您的账户信息已更新"
        });
    }
    
    self.init = init;
}

// 初始化模块
UserModule.init();
NotificationModule.init();

// 测试事件总线
GlobalEventBus.publish("user.create", {
    name: "张三",
    email: "zhangsan@example.com",
    role: "user"
});

4.4 拖拽交互

拖拽事件处理

1. 基本拖拽实现

// 拖拽管理器
class DragManager {
    function this() {
        this.isDragging = false;
        this.dragElement = null;
        this.dragData = null;
        this.dropZones = [];
        this.dragOffset = { x: 0, y: 0 };
        
        this.setupGlobalEvents();
    }
    
    function setupGlobalEvents() {
        var self = this;
        
        document.on("mousemove", function(evt) {
            if (self.isDragging) {
                self.handleDragMove(evt);
            }
        });
        
        document.on("mouseup", function(evt) {
            if (self.isDragging) {
                self.handleDragEnd(evt);
            }
        });
    }
    
    // 使元素可拖拽
    function makeDraggable(element, options = {}) {
        var self = this;
        
        element.on("mousedown", function(evt) {
            if (evt.button === 0) { // 左键
                self.startDrag(this, evt, options);
            }
        });
        
        element.addClass("draggable");
        element.style.cursor = "grab";
    }
    
    // 注册放置区域
    function registerDropZone(element, options = {}) {
        this.dropZones.push({
            element: element,
            options: options
        });
        
        element.addClass("drop-zone");
    }
    
    // 开始拖拽
    function startDrag(element, evt, options) {
        this.isDragging = true;
        this.dragElement = element;
        this.dragData = options.data || {};
        
        // 计算拖拽偏移
        var rect = element.getBoundingClientRect();
        this.dragOffset.x = evt.clientX - rect.left;
        this.dragOffset.y = evt.clientY - rect.top;
        
        // 创建拖拽代理
        this.createDragProxy(element, evt);
        
        // 添加拖拽样式
        element.addClass("dragging");
        document.body.addClass("drag-active");
        
        // 触发拖拽开始事件
        this.fireDragEvent("dragstart", element, evt);
        
        evt.preventDefault();
    }
    
    // 创建拖拽代理
    function createDragProxy(element, evt) {
        this.dragProxy = element.cloneNode(true);
        this.dragProxy.addClass("drag-proxy");
        this.dragProxy.style.position = "absolute";
        this.dragProxy.style.pointerEvents = "none";
        this.dragProxy.style.zIndex = "9999";
        this.dragProxy.style.opacity = "0.8";
        
        document.body.append(this.dragProxy);
        
        this.updateProxyPosition(evt);
    }
    
    // 更新代理位置
    function updateProxyPosition(evt) {
        if (this.dragProxy) {
            this.dragProxy.style.left = (evt.clientX - this.dragOffset.x) + "px";
            this.dragProxy.style.top = (evt.clientY - this.dragOffset.y) + "px";
        }
    }
    
    // 处理拖拽移动
    function handleDragMove(evt) {
        this.updateProxyPosition(evt);
        
        // 检查放置区域
        var dropZone = this.getDropZoneAt(evt.clientX, evt.clientY);
        this.updateDropZoneHighlight(dropZone);
        
        // 触发拖拽移动事件
        this.fireDragEvent("dragmove", this.dragElement, evt, dropZone);
    }
    
    // 处理拖拽结束
    function handleDragEnd(evt) {
        var dropZone = this.getDropZoneAt(evt.clientX, evt.clientY);
        
        if (dropZone && this.canDrop(dropZone, this.dragData)) {
            this.handleDrop(dropZone, evt);
        } else {
            this.handleDragCancel(evt);
        }
        
        this.cleanup();
    }
    
    // 处理放置
    function handleDrop(dropZone, evt) {
        var dropData = {
            dragElement: this.dragElement,
            dragData: this.dragData,
            dropZone: dropZone.element,
            position: { x: evt.clientX, y: evt.clientY }
        };
        
        // 触发放置事件
        this.fireDragEvent("drop", dropZone.element, evt, dropData);
        
        // 执行放置回调
        if (dropZone.options.onDrop) {
            dropZone.options.onDrop(dropData);
        }
    }
    
    // 处理拖拽取消
    function handleDragCancel(evt) {
        this.fireDragEvent("dragcancel", this.dragElement, evt);
    }
    
    // 清理拖拽状态
    function cleanup() {
        this.isDragging = false;
        
        if (this.dragElement) {
            this.dragElement.removeClass("dragging");
            this.dragElement = null;
        }
        
        if (this.dragProxy) {
            this.dragProxy.remove();
            this.dragProxy = null;
        }
        
        document.body.removeClass("drag-active");
        this.clearDropZoneHighlights();
        
        this.dragData = null;
    }
    
    // 获取指定位置的放置区域
    function getDropZoneAt(x, y) {
        for (var dropZone of this.dropZones) {
            var rect = dropZone.element.getBoundingClientRect();
            if (x >= rect.left && x <= rect.right && 
                y >= rect.top && y <= rect.bottom) {
                return dropZone;
            }
        }
        return null;
    }
    
    // 检查是否可以放置
    function canDrop(dropZone, dragData) {
        if (dropZone.options.accept) {
            if (typeof dropZone.options.accept === "function") {
                return dropZone.options.accept(dragData);
            } else {
                return dropZone.options.accept.includes(dragData.type);
            }
        }
        return true;
    }
    
    // 更新放置区域高亮
    function updateDropZoneHighlight(activeDropZone) {
        for (var dropZone of this.dropZones) {
            if (dropZone === activeDropZone) {
                dropZone.element.addClass("drop-zone-active");
            } else {
                dropZone.element.removeClass("drop-zone-active");
            }
        }
    }
    
    // 清除放置区域高亮
    function clearDropZoneHighlights() {
        for (var dropZone of this.dropZones) {
            dropZone.element.removeClass("drop-zone-active");
        }
    }
    
    // 触发拖拽事件
    function fireDragEvent(type, element, originalEvent, data) {
        var event = new CustomEvent(type, {
            dragElement: this.dragElement,
            dragData: this.dragData,
            originalEvent: originalEvent,
            data: data
        });
        
        element.dispatchEvent(event);
    }
}

// 创建全局拖拽管理器
var dragManager = new DragManager();

// 使用示例
function setupDragAndDrop() {
    // 使卡片可拖拽
    $$("#card-container .card").forEach(function(card) {
        dragManager.makeDraggable(card, {
            data: {
                type: "card",
                id: card.getAttribute("data-id"),
                content: card.innerText
            }
        });
    });
    
    // 注册放置区域
    $$("#drop-zones .zone").forEach(function(zone) {
        dragManager.registerDropZone(zone, {
            accept: ["card"],
            onDrop: function(dropData) {
                handleCardDrop(dropData);
            }
        });
    });
    
    // 监听拖拽事件
    document.on("dragstart", function(evt) {
        stdout.println("开始拖拽: " + evt.data.dragData.id);
    });
    
    document.on("drop", function(evt) {
        stdout.println("放置完成: " + evt.data.dragData.id);
    });
}

function handleCardDrop(dropData) {
    var card = dropData.dragElement;
    var zone = dropData.dropZone;
    
    // 移动卡片到新区域
    zone.append(card);
    
    // 更新数据
    updateCardPosition(dropData.dragData.id, zone.getAttribute("data-zone"));
    
    // 显示成功消息
    showMessage("卡片已移动到 " + zone.getAttribute("data-name"));
}

4.5 本章总结

在本章中,我们深入学习了Sciter的事件处理与交互机制:

事件系统

  • DOM事件的绑定和处理
  • 事件流程和传播机制
  • 常用事件类型的处理方法

自定义事件

  • 自定义事件的创建和触发
  • 事件发射器模式
  • 应用级事件系统设计

高级交互

  • 拖拽功能的实现
  • 事件总线模式
  • 模块间通信机制

实践技巧

  • 事件处理的最佳实践
  • 性能优化技巧
  • 错误处理和调试方法

4.6 练习题

基础练习

  1. 创建一个响应式的按钮组件,支持多种交互状态
  2. 实现一个简单的表单验证系统
  3. 制作一个可拖拽排序的列表组件
  4. 设计一个键盘快捷键系统

进阶练习

  1. 实现一个复杂的拖拽看板系统
  2. 创建一个事件驱动的状态管理系统
  3. 设计一个可扩展的插件事件系统
  4. 实现一个手势识别系统

挑战练习

  1. 创建一个完整的图形编辑器交互系统
  2. 实现一个多点触控支持的交互界面
  3. 设计一个实时协作的事件同步系统
  4. 制作一个游戏级别的输入处理系统

下一章预告: 在第5章中,我们将学习Sciter的窗口管理与布局系统,包括窗口创建、管理、布局算法,以及如何创建复杂的多窗口应用程序。