Qt+Cef3离屏渲染(二)

还是以windows上vs2017 release版本运行的程序为例。

接上一篇,我们只是将离屏渲染的每一帧图片QImage,以异步事件的方法发送到了目标QWiget上,然后再将QImage绘制到屏幕上。
留下了一些待处理的问题:

  1. 滚轮事件
  2. 鼠标的点击事件
  3. 鼠标hover到链接上,鼠标形状的变化
  4. 输入框的右键弹出菜单位置问题
  5. 键盘输入
  6. 按F5刷新网页

我们这篇博客就围绕这些问题进行处理。

一、离屏渲染的滚轮事件的处理

滚轮事件的处理最为简单。

在CefOSRWidget头文件中:

protected:
	//其余代码与前面一篇相同,这里省略
    void wheelEvent(QWheelEvent *event) override;
void CefOSRWidget::wheelEvent(QWheelEvent *event)
{
    QPoint pos = event->pos();

    CefRefPtr<CefBrowser> pBrower = getBrowser();
    if (pBrower)
    {
        CefMouseEvent cefMouseEvent;
        cefMouseEvent.x = pos.x();
        cefMouseEvent.y = pos.y();

        pBrower->GetHost()->SendMouseWheelEvent(cefMouseEvent, 0, event->delta());
    }

    //因为不必传递给下层,所以下面这行注释掉
    //QWidget::wheelEvent(event);
}

cef_browser.h中的定义

class CefBrowserHost : public virtual CefBaseRefCounted {
 public:
 //其余省略
 
 // Send a mouse wheel event to the browser. The |x| and |y| coordinates are
  // relative to the upper-left corner of the view. The |deltaX| and |deltaY|
  // values represent the movement delta in the X and Y directions respectively.
  // In order to scroll inside select popups with window rendering disabled
  // CefRenderHandler::GetScreenPoint should be implemented properly.
  ///
  /*--cef()--*/
  virtual void SendMouseWheelEvent(const CefMouseEvent& event,
                                   int deltaX,
                                   int deltaY) = 0;
//其余省略
};

就是向该Cef浏览器发送一个滚轮事件。

本篇博客中的其余做法,也是将手动发送事件给Cef浏览器。

效果:

在这里插入图片描述
垂直滚轮有了效果,但是发现鼠标点击滚动条两端的按钮没有效果,这时候,我们就需要手动发送鼠标事件给Cef浏览器了。

二、离屏渲染的鼠标事件的处理

class CefOSRWidget : public QWidget
{
    Q_OBJECT

public:
	//其余省略
protected:
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void mouseDoubleClickEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    //其余省略
    
private:
    //将Qt的鼠标按钮转换成Cef中的鼠标按键(鼠标左键、中键,右键)
    cef_mouse_button_type_t toCefMouseButton(Qt::MouseButton mouseButton);

cefosrwidget.cpp中相应的定义:


void CefOSRWidget::mousePressEvent(QMouseEvent *event)
{
    QPoint pos = event->pos();

    CefRefPtr<CefBrowser> pBrower = getBrowser();
    if (pBrower)
    {
        CefMouseEvent cefMouseEvent;
        cefMouseEvent.x = pos.x();
        cefMouseEvent.y = pos.y();

        cef_mouse_button_type_t cefMouseButton = toCefMouseButton(event->button());

        pBrower->GetHost()->SendMouseClickEvent(cefMouseEvent, cefMouseButton, false, 1);
    }
    //QWidget::mousePressEvent(event);
}

void CefOSRWidget::mouseReleaseEvent(QMouseEvent *event)
{
    QPoint pos = event->pos();

    CefRefPtr<CefBrowser> pBrower = getBrowser();
    if (pBrower)
    {
        CefMouseEvent cefMouseEvent;
        cefMouseEvent.x = pos.x();
        cefMouseEvent.y = pos.y();

        cef_mouse_button_type_t cefMouseButton = toCefMouseButton(event->button());
        pBrower->GetHost()->SendMouseClickEvent(cefMouseEvent, cefMouseButton, true, 1);
    }
    //QWidget::mouseReleaseEvent(event);
}

void CefOSRWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
    QPoint pos = event->pos();

    CefRefPtr<CefBrowser> pBrower = getBrowser();
    if (pBrower)
    {
        CefMouseEvent cefMouseEvent;
        cefMouseEvent.x = pos.x();
        cefMouseEvent.y = pos.y();

        cef_mouse_button_type_t cefMouseButton = toCefMouseButton(event->button());
        pBrower->GetHost()->SendMouseClickEvent(cefMouseEvent, cefMouseButton, false, 2);
    }
    //QWidget::mouseDoubleClickEvent(event);
}

void CefOSRWidget::mouseMoveEvent(QMouseEvent *event)
{
    QPoint pos = event->pos();

    CefRefPtr<CefBrowser> pBrower = getBrowser();
    if (pBrower)
    {
        CefMouseEvent cefMouseEvent;
        cefMouseEvent.x = pos.x();
        cefMouseEvent.y = pos.y();
        cefMouseEvent.modifiers = EVENTFLAG_LEFT_MOUSE_BUTTON;

        pBrower->GetHost()->SendMouseMoveEvent(cefMouseEvent, false);
    }
    //QWidget::mouseMoveEvent(event);
}

cef_mouse_button_type_t CefOSRWidget::toCefMouseButton(Qt::MouseButton mouseButton)
{
    cef_mouse_button_type_t cefMouseButton = MBT_LEFT;
    switch (mouseButton)
    {
        case Qt::LeftButton:
            cefMouseButton = MBT_LEFT;
            break;

        case Qt::RightButton:
            cefMouseButton = MBT_RIGHT;
            break;

        case Qt::MiddleButton:
            cefMouseButton = MBT_MIDDLE;
        break;

    default:
        break;
    }
    return cefMouseButton;
}

小提示:
(1)cefosrwidget.ui文件中:
在这里插入图片描述
这里没有再嵌套QWidget,或者布局,就是为了避免坐标转换。刚开始的时候,我还是像前面几篇博客中一样,添加了一个contentWidget的QWiget在界面中,因此处理事件中的坐标的时候,每次都需要进行转换,最后发现什么都不要加,直接用这个QWidget来显示Cef,就不需要什么坐标转换了。

(2)关于Qt中事件的传递,我在做无边框窗口的移动的时候,如果在CefOSRWidget的鼠标事件中调用了QWidget::mouseXXEvent(event);则会将鼠标事件传递下去,这个在无边框可移动窗口中,会导致一些问题,正确的做法是在CefOSRWidget中的鼠标事件只自己处理,不再进行传递。事件的响应次序可以查看我之前的博客。

三、鼠标hover到链接上,鼠标形状的变化

平时,我们浏览网页的时候,鼠标hover到连接,或者一些按钮上会变成手掌的形状,而我们的离屏渲染窗口中缺没有这样鼠标形状变化。我个人的摸索,发现是CefOSRWidget要

setMouseTracking(true);

这样鼠标在没有按下的时候,也会一直检测鼠标的位置,一直响应mouseMoveEvent事件。

  // Send a mouse move event to the browser. The |x| and |y| coordinates are
  // relative to the upper-left corner of the view.
  ///
  /*--cef()--*/
  virtual void SendMouseMoveEvent(const CefMouseEvent& event,
                                  bool mouseLeave) = 0;

二中已经在

void CefOSRWidget::mouseMoveEvent(QMouseEvent *event)

中添加了

pBrower->GetHost()->SendMouseMoveEvent(cefMouseEvent, false);

但是在我们的Cef窗口中,为什么鼠标形状还是没有变化呢?

于是我们回头去CefRenderHandler类中找:

class CefRenderHandler : public virtual CefBaseRefCounted {
 public:
 //中间省略

  // Called when the browser's cursor has changed. If |type| is CT_CUSTOM then
  // |custom_cursor_info| will be populated with the custom cursor information.
  ///
  /*--cef()--*/
  virtual void OnCursorChange(CefRefPtr<CefBrowser> browser,
                              CefCursorHandle cursor,
                              CursorType type,
                              const CefCursorInfo& custom_cursor_info) {}
};

原来是需要我们重写OnCursorChange
在simple_handler.h文件中:

class SimpleHandler : public CefClient,
                      public CefLifeSpanHandler,
                      public CefLoadHandler,
                      public CefFocusHandler,
                      public CefDisplayHandler,
                      public CefRenderHandler //新加的

{
public:
//其余省略
  virtual void OnCursorChange(CefRefPtr<CefBrowser> browser,
                              CefCursorHandle cursor,
                              CursorType type,
                              const CefCursorInfo& custom_cursor_info) OVERRIDE;
};

在simple_handler.cpp中

void SimpleHandler::OnCursorChange(CefRefPtr<CefBrowser> browser, CefCursorHandle cursor, CefRenderHandler::CursorType type, const CefCursorInfo &custom_cursor_info)
{
    ChangeCursorEvent *changeCursorEvent = new ChangeCursorEvent(type);
    QCoreApplication::postEvent(m_receiver, changeCursorEvent);

//    ::SetCursor(cursor);
}

直接调用::SetCursor(cursor);,不起作用,这个是windows API,改变鼠标形状的。
类似的,我们要自定义一个鼠标形状时间,将每次改变后的鼠标形状事件,发送到CefOSRWidget,在CefOSRWidget中进行改变。

class ChangeCursorEvent : public QEvent
{
public:
    const static Type eventType = static_cast<Type>(QEvent::User+2);
    explicit ChangeCursorEvent(cef_cursor_type_t shape_);
    cef_cursor_type_t shape;
};

ChangeCursorEvent::ChangeCursorEvent(cef_cursor_type_t shape_) :QEvent(eventType)
{
    shape = shape_;
}

在CefOSRWidget的event函数中:

bool CefOSRWidget::event(QEvent *event)
{
    if (event->type() == UpdateEvent::eventType)
    {
        UpdateEvent *updateEvent = dynamic_cast<UpdateEvent*>(event);
        m_image = updateEvent->image;
        this->update();

        return true;
    }
    else if (event->type() == ChangeCursorEvent::eventType)
    {
        ChangeCursorEvent *changeCursorEvent = dynamic_cast<ChangeCursorEvent*>(event);

        cef_cursor_type_t cursorShape = changeCursorEvent->shape;
        Qt::CursorShape qtCursorShape = Qt::ArrowCursor;

        switch (cursorShape)
        {
            case CT_POINTER:
                qtCursorShape = Qt::ArrowCursor;
            break;

            case CT_CROSS:
                qtCursorShape = Qt::CrossCursor;
            break;

            case CT_HAND:
                qtCursorShape = Qt::PointingHandCursor;
            break;

            case CT_IBEAM:
                qtCursorShape = Qt::IBeamCursor;
            break;

            case CT_WAIT:
                qtCursorShape = Qt::WaitCursor;
            break;

            default:
                break;
        }

        this->setCursor(qtCursorShape);

        return true;
    }
    return QWidget::event(event);
}

这里我们只处理了部分的鼠标形状,如果要做到完善的话,可以自定添加。

在这里插入图片描述
这时候,我们再运行,就看到了鼠标hover到链接上改变形状。

四、输入框的右键弹出菜单位置问题

我们在输入框右键的时候,弹出了右键菜单,可是这个右键菜单的问题有问题,我们来解决它。
在这里插入图片描述
同样的,在simple_handler.h类中去重写GetScreenPoint,这个也是CefRenderHandler类中虚函数。

  // Called to retrieve the translation from view coordinates to actual screen
  // coordinates. Return true if the screen coordinates were provided.
  ///
  /*--cef()--*/
  virtual bool GetScreenPoint(CefRefPtr<CefBrowser> browser,
                              int viewX,
                              int viewY,
                              int& screenX,
                              int& screenY) OVERRIDE;

simple_handler.cpp中

bool SimpleHandler::GetScreenPoint(CefRefPtr<CefBrowser> browser, int viewX, int viewY, int &screenX, int &screenY)
{
    RECT clientRect;
    if (!::GetWindowRect(m_hwnd, &clientRect))
        return false;

    screenX = clientRect.left + viewX;
    screenY = clientRect.top + viewY;
    return true;
}

运行效果:
在这里插入图片描述
这回右键菜单的位置大致差不多了。

五、键盘输入

在输入框中,发现键盘无法输入,我们同样需要在CefOSRWidget中添加:

cefosrwidget.h中添加

protected:
void keyPressEvent(QKeyEvent *event) override;

private:
    CefKeyEvent toCefKeyEvent(QKeyEvent *event);

cefosrwidget.cpp文件中:

void CefOSRWidget::keyPressEvent(QKeyEvent *event)
{
    CefRefPtr<CefBrowser> pBrower = getBrowser();
    if (pBrower)
    {
        pBrower->GetHost()->SendKeyEvent(toCefKeyEvent(event));
    }

    QWidget::keyPressEvent(event);
}

CefKeyEvent CefOSRWidget::toCefKeyEvent(QKeyEvent *event)
{
    CefKeyEvent cefKeyEvent;
    cefKeyEvent.type = KEYEVENT_CHAR;

    Qt::KeyboardModifiers keyboardModifiers = event->modifiers();
    qDebug() << "keyboardModifiers:" << keyboardModifiers;

    //只对Shift键和Ctrl键做处理,其余按需要进行处理的话(自己添加代码)
    switch (keyboardModifiers)
    {
    case Qt::ShiftModifier:
        cefKeyEvent.modifiers = EVENTFLAG_SHIFT_DOWN;
        cefKeyEvent.type = KEYEVENT_CHAR;  //如果是按下了shit键的话,则这里需要让Cef知道另外普通键的输入
        break;

    case Qt::ControlModifier:
       cefKeyEvent.modifiers = EVENTFLAG_CONTROL_DOWN;
       cefKeyEvent.type = KEYEVENT_KEYDOWN;
       break;

//    case Qt::AltModifier:
//      cefKeyEvent.modifiers = EVENTFLAG_ALT_DOWN;
//      cefKeyEvent.type = KEYEVENT_KEYDOWN;
//      break;

    default:
       break;
    }

    int key = event->key();

    qDebug() << "key: " << event->key();
    qDebug() << "nativeScanCode: " << event->nativeScanCode();
    qDebug() << "nativeVirtualKey: " << event->nativeVirtualKey();

    cefKeyEvent.windows_key_code = event->key();

    if (event->key() == Qt::Key_Backspace)
    {
        qDebug() << "event->key() == Qt::Key_Backspace";
        cefKeyEvent.type = KEYEVENT_KEYDOWN;
        cefKeyEvent.windows_key_code = static_cast<int>(event->nativeVirtualKey());
    }

    if (key >= Qt::Key_F1 && key <= Qt::Key_F12)
    {
        qDebug() << "Qt::Key_F1";
        cefKeyEvent.type = KEYEVENT_KEYDOWN;
    }

    return cefKeyEvent;
}

运行结果:
在这里插入图片描述
可以输入,但是只能输入数字、符号和英文大写,无法输入中文。

本教程只是个抛转引入,因为Cef wiki文档上语焉不详,有些需要自己摸索。

个人猜测:需要设置Cef能接受的语言,比如中文,在键盘输入事件中,去区分大小写等。

六、按键盘上的F5键刷新网页
(1)自定义事件

class ReloadBrowerEvent : public QEvent
{
public:
    const static Type eventType = static_cast<Type>(QEvent::User+3);
    explicit ReloadBrowerEvent();
};

ReloadBrowerEvent::ReloadBrowerEvent() :QEvent(eventType)
{
}

(2)在cefosrwidget.cpp中

CefKeyEvent CefOSRWidget::toCefKeyEvent(QKeyEvent *event)
{
	//其余代码与上面相同,这里省略
    if (event->key() == Qt::Key_F5)
    {
        qDebug() << "Qt::Key_F5"; //刷新
        ReloadBrowerEvent *reloadBrowerEvent = new ReloadBrowerEvent();
        QCoreApplication::sendEvent(this, reloadBrowerEvent);
    }
    return cefKeyEvent;
}
bool CefOSRWidget::event(QEvent *event)
{
    if (event->type() == UpdateEvent::eventType)
    {
        UpdateEvent *updateEvent = dynamic_cast<UpdateEvent*>(event);
        m_image = updateEvent->image;
        this->update();

        return true;
    }
    else if (event->type() == ChangeCursorEvent::eventType)
    {
        ChangeCursorEvent *changeCursorEvent = dynamic_cast<ChangeCursorEvent*>(event);

        cef_cursor_type_t cursorShape = changeCursorEvent->shape;
        Qt::CursorShape qtCursorShape = Qt::ArrowCursor;

        switch (cursorShape)
        {
            case CT_POINTER:
                qtCursorShape = Qt::ArrowCursor;
            break;

            case CT_CROSS:
                qtCursorShape = Qt::CrossCursor;
            break;

            case CT_HAND:
                qtCursorShape = Qt::PointingHandCursor;
            break;

            case CT_IBEAM:
                qtCursorShape = Qt::IBeamCursor;
            break;

            case CT_WAIT:
                qtCursorShape = Qt::WaitCursor;
            break;

            default:
                break;
        }

        this->setCursor(qtCursorShape);

        return true;
    }else if (event->type() == ReloadBrowerEvent::eventType)
    {
        CefRefPtr<CefBrowser> pBrower = getBrowser();
        if (pBrower)
        {
            pBrower->Reload();
        }
    }
    return QWidget::event(event);
}

按F5就能刷新网页了。

百度云盘
链接:https://pan.baidu.com/s/1NFJhg0xhRvmA1h4dpzyniA
提取码:2kp5

个人的一点体会:

在离屏渲染事件中,一般是CefRenderHandler中相应的接口,要么在QWidget上将事件传递给Cef,要么是在CefRenderHandler中相应的接口中获取变化后的数据,再用postEvent发送自定义事件,将该数据传递给QWidget,进行相应处理。

Qt相关知识:
(1)事件的传递顺序
(2)postEvent和sendEvent的区别

接下来需要注意的问题:

Cef关闭流程,如果关闭流程处理不好,则会出现关闭的时候崩溃的问题,Cef关闭流程将在接下来的教程进行介绍。

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值