跳转到内容

5.3 事件处理器

事件处理器(Event Handlers)允许你封装可重用的交互逻辑,实现代码的模块化和复用。本节将深入讲解如何创建高级的事件处理器。


一、基础事件处理器

1.1 EventListener 接口

cpp
class EventListener {
public:
    virtual ~EventListener() = default;
    
    // 处理事件
    virtual void ProcessEvent(Event& event) = 0;
};

1.2 创建基础处理器

cpp
// ClickHandler.h
#pragma once
#include <RmlUi/Core/EventListener.h>
#include <functional>

class ClickHandler : public Rml::EventListener
{
public:
    using ClickCallback = std::function<void(Rml::Event&)>;

    ClickHandler(ClickCallback callback);
    void ProcessEvent(Rml::Event& event) override;

private:
    ClickCallback callback_;
};

// ClickHandler.cpp
ClickHandler::ClickHandler(ClickCallback callback)
    : callback_(std::move(callback))
{
}

void ClickHandler::ProcessEvent(Rml::Event& event)
{
    if (event.GetId() == Rml::EventId::Click)
    {
        callback_(event);
    }
}

// 使用示例
void OnStartButtonClick(Rml::Event& event)
{
    Rml::Element* button = event.GetCurrentElement();
    printf("Button clicked: %s\n", button->GetId().c_str());
}

// 注册
Rml::Element* button = document->GetElementById("btn-start");
button->AddEventListener(Rml::EventId::Click, new ClickHandler(OnStartButtonClick));

二、高级事件处理器

2.1 拖拽处理器

cpp
// DragHandler.h
#pragma once
#include <RmlUi/Core/EventListener.h>
#include <RmlUi/Core/Element.h>

class DragHandler : public Rml::EventListener
{
public:
    DragHandler(Rml::Element* target, Rml::Element* handle);
    ~DragHandler();

    void ProcessEvent(Rml::Event& event) override;

private:
    Rml::Element* target_;
    Rml::Element* handle_;
    bool is_dragging_;
    Rml::Vector2f drag_offset_;

    void StartDrag(Rml::Event& event);
    void OnDrag(Rml::Event& event);
    void EndDrag(Rml::Event& event);
};

// DragHandler.cpp
DragHandler::DragHandler(Rml::Element* target, Rml::Element* handle)
    : target_(target), handle_(handle), is_dragging_(false)
{
    if (handle_)
    {
        handle_->AddEventListener(Rml::EventId::Mousedown, this);
    }
}

DragHandler::~DragHandler()
{
    if (handle_)
    {
        handle_->RemoveEventListener(Rml::EventId::Mousedown, this);
    }
}

void DragHandler::ProcessEvent(Rml::Event& event)
{
    switch (event.GetId())
    {
        case Rml::EventId::Mousedown:
            StartDrag(event);
            break;
        case Rml::EventId::Mousemove:
            OnDrag(event);
            break;
        case Rml::EventId::Mouseup:
            EndDrag(event);
            break;
    }
}

void DragHandler::StartDrag(Rml::Event& event)
{
    if (event.GetCurrentElement() == handle_)
    {
        is_dragging_ = true;
        Rml::Vector2f mouse_pos = event.GetParameter<Rml::Vector2f>("mouse_pos", Rml::Vector2f(0, 0));
        drag_offset_ = mouse_pos - target_->GetAbsoluteLeftTop();
        
        target_->SetProperty(Rml::PropertyId::Cursor, Rml::Property(Rml::Style::Cursor::Move));
        
        // 监听全局移动和释放
        target_->GetOwnerDocument()->AddEventListener(Rml::EventId::Mousemove, this);
        target_->GetOwnerDocument()->AddEventListener(Rml::EventId::Mouseup, this);
    }
}

void DragHandler::OnDrag(Rml::Event& event)
{
    if (is_dragging_)
    {
        Rml::Vector2f mouse_pos = event.GetParameter<Rml::Vector2f>("mouse_pos", Rml::Vector2f(0, 0));
        Rml::Vector2f new_pos = mouse_pos - drag_offset_;
        
        target_->SetProperty(Rml::PropertyId::Left, Rml::Property(new_pos.x, Rml::Unit::PX));
        target_->SetProperty(Rml::PropertyId::Top, Rml::Property(new_pos.y, Rml::Unit::PX));
    }
}

void DragHandler::EndDrag(Rml::Event& event)
{
    if (is_dragging_)
    {
        is_dragging_ = false;
        target_->SetProperty(Rml::PropertyId::Cursor, Rml::Property(Rml::Style::Cursor::Auto));
        
        target_->GetOwnerDocument()->RemoveEventListener(Rml::EventId::Mousemove, this);
        target_->GetOwnerDocument()->RemoveEventListener(Rml::EventId::Mouseup, this);
    }
}

2.2 快捷键处理器

cpp
// ShortcutHandler.h
#pragma once
#include <RmlUi/Core/EventListener.h>
#include <RmlUi/Core/Input.h>
#include <unordered_map>
#include <functional>

class ShortcutHandler : public Rml::EventListener
{
public:
    struct ShortcutKey {
        Rml::Input::KeyIdentifier key;
        bool ctrl;
        bool shift;
        bool alt;
        
        bool operator==(const ShortcutKey& other) const
        {
            return key == other.key && 
                   ctrl == other.ctrl && 
                   shift == other.shift && 
                   alt == other.alt;
        }
    };
    
    struct ShortcutKeyHash {
        std::size_t operator()(const ShortcutKey& k) const
        {
            return std::hash<int>()(k.key) ^ 
                   (std::hash<bool>()(k.ctrl) << 1) ^ 
                   (std::hash<bool>()(k.shift) << 2) ^ 
                   (std::hash<bool>()(k.alt) << 3);
        }
    };
    
    using ShortcutCallback = std::function<void()>;

    ShortcutHandler();
    
    void RegisterShortcut(Rml::Input::KeyIdentifier key, ShortcutCallback callback,
                         bool ctrl = false, bool shift = false, bool alt = false);
    
    void ProcessEvent(Rml::Event& event) override;

private:
    std::unordered_map<ShortcutKey, ShortcutCallback, ShortcutKeyHash> shortcuts_;
};

// ShortcutHandler.cpp
ShortcutHandler::ShortcutHandler()
{
}

void ShortcutHandler::RegisterShortcut(Rml::Input::KeyIdentifier key, ShortcutCallback callback,
                                      bool ctrl, bool shift, bool alt)
{
    ShortcutKey shortcut_key = {key, ctrl, shift, alt};
    shortcuts_[shortcut_key] = std::move(callback);
}

void ShortcutHandler::ProcessEvent(Rml::Event& event)
{
    if (event.GetId() == Rml::EventId::Keydown)
    {
        Rml::Input::KeyIdentifier key = event.GetParameter<Rml::Input::KeyIdentifier>("key_code");
        bool ctrl = event.GetParameter<bool>("ctrl_key", false);
        bool shift = event.GetParameter<bool>("shift_key", false);
        bool alt = event.GetParameter<bool>("alt_key", false);
        
        ShortcutKey shortcut_key = {key, ctrl, shift, alt};
        auto it = shortcuts_.find(shortcut_key);
        if (it != shortcuts_.end())
        {
            it->second();
            event.StopPropagation();
        }
    }
}

三、事件处理器工厂

3.1 工厂模式

cpp
// EventHandlerFactory.h
#pragma once
#include <RmlUi/Core/EventListener.h>
#include <functional>
#include <memory>

class EventHandlerFactory
{
public:
    static std::unique_ptr<Rml::EventListener> CreateClickHandler(
        std::function<void(Rml::Event&)> callback)
    {
        return std::make_unique<ClickHandler>(std::move(callback));
    }

    static std::unique_ptr<Rml::EventListener> CreateDragHandler(
        Rml::Element* target, Rml::Element* handle)
    {
        return std::make_unique<DragHandler>(target, handle);
    }

    static std::unique_ptr<Rml::EventListener> CreateHoverHandler(
        std::function<void(Rml::Event&)> on_enter,
        std::function<void(Rml::Event&)> on_leave)
    {
        return std::make_unique<HoverHandler>(std::move(on_enter), std::move(on_leave));
    }
};

// HoverHandler.h
class HoverHandler : public Rml::EventListener
{
public:
    HoverHandler(std::function<void(Rml::Event&)> on_enter,
                 std::function<void(Rml::Event&)> on_leave)
        : on_enter_(std::move(on_enter)), on_leave_(std::move(on_leave))
    {
    }

    void ProcessEvent(Rml::Event& event) override
    {
        if (event.GetId() == Rml::EventId::Mouseover)
        {
            on_enter_(event);
        }
        else if (event.GetId() == Rml::EventId::Mouseout)
        {
            on_leave_(event);
        }
    }

private:
    std::function<void(Rml::Event&)> on_enter_;
    std::function<void(Rml::Event&)> on_leave_;
};

四、事件处理器管理器

4.1 管理器实现

cpp
// EventHandlerManager.h
#pragma once
#include <RmlUi/Core/EventListener.h>
#include <unordered_map>
#include <memory>

class EventHandlerManager
{
public:
    static EventHandlerManager& Instance()
    {
        static EventHandlerManager instance;
        return instance;
    }

    // 注册处理器
    void RegisterHandler(const Rml::String& name, std::unique_ptr<Rml::EventListener> handler)
    {
        handlers_[name] = std::move(handler);
    }

    // 获取处理器
    Rml::EventListener* GetHandler(const Rml::String& name)
    {
        auto it = handlers_.find(name);
        return it != handlers_.end() ? it->second.get() : nullptr;
    }

    // 清除所有处理器
    void Clear()
    {
        handlers_.clear();
    }

private:
    EventHandlerManager() = default;
    std::unordered_map<Rml::String, std::unique_ptr<Rml::EventListener>> handlers_;
};

// 使用示例
EventHandlerManager::Instance().RegisterHandler(
    "drag_window",
    EventHandlerFactory::CreateDragHandler(window, header)
);

// 绑定到元素
Rml::Element* element = document->GetElementById("my-element");
element->AddEventListener(Rml::EventId::Click, 
    EventHandlerManager::Instance().GetHandler("drag_window"));

五、实战示例

5.1 完整的窗口管理器

cpp
// WindowManager.h
#pragma once
#include <RmlUi/Core/EventListener.h>
#include <RmlUi/Core/Element.h>
#include <vector>

class WindowManager : public Rml::EventListener
{
public:
    WindowManager(Rml::Context* context);
    ~WindowManager();

    // 创建窗口
    Rml::Element* CreateWindow(const Rml::String& title, float x, float y, float width, float height);

    // 关闭窗口
    void CloseWindow(Rml::Element* window);

    void ProcessEvent(Rml::Event& event) override;

private:
    struct WindowData {
        Rml::Element* window;
        Rml::Element* header;
        Rml::Element* content;
        Rml::Element* close_button;
        std::unique_ptr<DragHandler> drag_handler;
    };

    Rml::Context* context_;
    std::vector<std::unique_ptr<WindowData>> windows_;
    WindowData* active_window_;
    WindowData* dragged_window_;

    void SetupWindowEvents(WindowData* window_data);
    void BringToFront(WindowData* window_data);
};

六、最佳实践

6.1 内存管理

cpp
// 使用智能指针管理处理器生命周期
class ManagedEventHandler
{
private:
    std::vector<std::unique_ptr<Rml::EventListener>> handlers_;

public:
    void AddHandler(Rml::Element* element, Rml::EventId id, 
                   std::unique_ptr<Rml::EventListener> handler)
    {
        element->AddEventListener(id, handler.get());
        handlers_.push_back(std::move(handler));
    }
};

6.2 性能优化

cpp
// 使用事件委托减少监听器数量
class EventDelegate : public Rml::EventListener
{
public:
    void ProcessEvent(Rml::Event& event) override
    {
        Rml::Element* target = event.GetTargetElement();
        Rml::String id = target->GetId();
        
        // 根据 ID 分发到不同的处理器
        auto it = handlers_.find(id);
        if (it != handlers_.end())
        {
            it->second(event);
        }
    }

    void RegisterHandler(const Rml::String& id, std::function<void(Rml::Event&)> handler)
    {
        handlers_[id] = std::move(handler);
    }

private:
    std::unordered_map<Rml::String, std::function<void(Rml::Event&)>> handlers_;
};

七、实战练习

练习 1:创建表单验证处理器

实现一个表单验证处理器:

  • 输入时验证
  • 显示错误提示
  • 禁用/启用提交按钮

练习 2:创建动画序列处理器

实现一个动画序列处理器:

  • 按顺序播放动画
  • 支持回调
  • 支持暂停/继续

练习 3:创建拖放处理器

实现一个拖放处理器:

  • 支持拖拽元素
  • 支持放置目标
  • 支持数据传递

八、下一步

继续学习 插件开发 来创建更复杂的系统集成。


📝 检查清单

基于 MIT 许可发布