官术网_书友最值得收藏!

  • Qt 5 Blueprints
  • Symeon Huang
  • 1301字
  • 2021-07-23 19:48:18

Tweaking the digital clock

It's time to make this basic digital clock look more beautiful. Let's add something like a transparent background, which sits on top of the frameless window. Using a transparent background can deliver a fantastic visual effect. While the frameless window hides window decorations, including a border and the title bar, a desktop widget, such as a clock, should be frameless and displayed on top of the desktop.

To make our clock translucent, simply add the following line to the constructor of MainWindow:

setAttribute(Qt::WA_TranslucentBackground);

The effect of the WA_TranslucentBackground attribute depends on the composition managers on the X11 platforms.

A widget may have lots of attributes, and this function is used to switch on or switch off a specified attribute. It's turned on by default. You need to pass a false Boolean as the second argument to disable an attribute. The full list of Qt::WidgetAttribute can be found in the Qt Reference Documentation.

Now, add the following line to the constructor as well, which will make the clock look frameless and make it stay on top of the desktop:

setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);

Similarly, Qt::WindowFlags is used to define the type of widget. It controls the behavior of the widget, rather than of its properties. Thus, two hints are given: one is to stay on top and the other is to be frameless. If you want to preserve old flags while setting new ones, you need to add them to the combination.

setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | windowFlags());

Here, the windowFlags function is used to retrieve the window flags. One thing you may be interested to know is that setWindowFlags will result in the invisibility of the widget after the show function. So, you can either call setWindowFlags before the show function of the window or widget or call show again after setWindowFlags.

After the modification to the constructor, this is how the clock is expected to look:

Tweaking the digital clock

There is a useful trick that you can use to hide the clock from the taskbar. Of course, a clock doesn't need to be displayed among the applications in a taskbar. You should never set a flag such as Qt::Tool or Qt::ToolTip alone to achieve this because this will cause the exit behavior of the application to be abnormal. This trick is even simpler; here is the code of main.cpp:

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
  QApplication a(argc, argv);

  QWidget wid;
  MainWindow w(&wid);
  w.show();

  return a.exec();
}

The preceding code makes our MainWindow w object a child of QWidget wid. The child widgets won't display on the taskbar because there should be only one top parent widget. Meanwhile, our parent widget, wid, doesn't even show. It's tricky, but it's the only one that does the trick without breaking any other logic.

Well, a new problem has just surfaced. The clock is unable to move and the only way to close it is by stopping it through the Qt Creator's panel or through a keyboard shortcut. This is because we declared it as a frameless window, which led to an inability to control it via a window manager. Since there is no way to interact with it, it's impossible to close it by itself. Hence, the solution to this problem is to write our own functions to move and close the clock.

Closing this application may be more urgent. Let's see how to reimplement some functions to achieve this goal. First, we need to declare a new showContextMenu slot to display a context menu and reimplement mouseReleaseEvent. The following code shows the content of mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
  class MainWindow;
}

class MainWindow : public QMainWindow
{
  Q_OBJECT
public:
  explicit MainWindow(QWidget *parent = 0);
  ~MainWindow();

private:
  Ui::MainWindow *ui;

private slots:
  void updateTime();
  void showContextMenu(const QPoint &pos);

protected:
  void mouseReleaseEvent(QMouseEvent *);
};

#endif // MAINWINDOW_H

There are two new classes defined in the preceding code: QPoint and QMouseEvent. The QPoint class defines a point in the plane by using integer precision. Relatively, there is another class named QPointF, which provides float precision. Well, the QMouseEvent class inherits QEvent and QInputEvent. It contains some parameters that describe a mouse event. Let's see why we need them in mainwindow.cpp:

#include <QTimer>
#include <QTime>
#include <QMouseEvent>
#include <QMenu>
#include <QAction>
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::MainWindow)
{
  ui->setupUi(this);

  setAttribute(Qt::WA_TranslucentBackground);
  setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint | windowFlags());

  connect(this, &MainWindow::customContextMenuRequested, this, &MainWindow::showContextMenu);

  QTimer *timer = new QTimer(this);
  connect(timer, &QTimer::timeout, this, &MainWindow::updateTime);
  timer->start(1000);

  updateTime();
}

MainWindow::~MainWindow()
{
  delete ui;
}

void MainWindow::updateTime()
{
  QTime currentTime = QTime::currentTime();
  QString currentTimeText = currentTime.toString("hh:mm");
  if (currentTime.second() % 2 == 0) {
    currentTimeText[2] = ' ';
  }
  ui->lcdNumber->display(currentTimeText);
}

void MainWindow::showContextMenu(const QPoint &pos)
{
  QMenu contextMenu;
  contextMenu.addAction(QString("Exit"), this, SLOT(close()));
  contextMenu.exec(mapToGlobal(pos));
}

void MainWindow::mouseReleaseEvent(QMouseEvent *e)
{
  if (e->button() == Qt::RightButton) {
    emit customContextMenuRequested(e->pos());
  }
  else {
    QMainWindow::mouseReleaseEvent(e);
  }
}

Note that you should include QMouseEvent, QMenu, and QAction in order to utilize these classes. There is a predefined customContextMenuRequested signal, which is coupled with the newly created showContextMenu slot. For the sake of consistency, we will follow the rule that Qt defined, which means that the QPoint argument in customContextMenuRequested should be a local position instead of a global position. That's why we need a mapToGlobal function to translate pos to a global position. As for the QMenu class, it provides a menu widget for a menu bar, context menu, or other pop-up menus. So, we create the contextMenu object, and then add a new action with the Exit text. This is coupled with a close slot of MainWindow. The last statement is used to execute the contextMenu object at the specified global position. In other words, this slot will display a pop-up menu at the given position.

The reimplementation of mouseReleaseEvent is done to check the triggered button of the event. If it's the right button, emit the customContextMenuRequested signal with the local position of the mouse. Otherwise, simply call the default mouseReleaseEvent function of QMainWindow.

Make use of the default member functions of the base class when you reimplement it.

Run the application again; you can quit by right-clicking on it and then selecting Exit. Now, we should continue the reimplementation to make the clock movable. This time, we need to rewrite two protected functions: mousePressEvent and mouseMoveEvent. Therefore, this is how the header file looks:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
  class MainWindow;
}

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  explicit MainWindow(QWidget *parent = 0);
  ~MainWindow();

private:
  Ui::MainWindow *ui;
  QPoint m_mousePos;

private slots:
  void updateTime();
  void showContextMenu(const QPoint &pos);

protected:
  void mouseReleaseEvent(QMouseEvent *);
  void mousePressEvent(QMouseEvent *);
  void mouseMoveEvent(QMouseEvent *);
};

#endif // MAINWINDOW_H

There is also a declaration of a new private member variable in the preceding code, m_mousePos, which is a QPoint object used to store the local position of the mouse. The following code defines mousePressEvent and mouseMoveEvent:

void MainWindow::mousePressEvent(QMouseEvent *e)
{
  m_mousePos = e->pos();
}

void MainWindow::mouseMoveEvent(QMouseEvent *e)
{
  this->move(e->globalPos() - m_mousePos);
}

It's easier than you thought. When a mouse button is pressed, the local position of the mouse is stored as m_mousePos. When the mouse is moving, we call the move function to move MainWindow to a new position. Because the position passed to move is a global position, we need to use globalPos of the event minus the local position of the mouse. Confused? The m_mousePos variable is the mouse's relative position to the top-left point of the parent widget, which is MainWindow in our case. The move function will move the top-left point of MainWindow to the given global position. While the e->globalPos() function is the global position of the mouse and not MainWindow, we need to subtract the relative position of m_mousePos to translate the mouse's global position to the top-left point position of MainWindow. After all this effort, the clock should look much more satisfying.

主站蜘蛛池模板: 张家口市| 闸北区| 道孚县| 临夏县| 禹州市| 贵定县| 沽源县| 海盐县| 赫章县| 西宁市| 奎屯市| 乌什县| 保定市| 扬州市| 五莲县| 安丘市| 古田县| 德化县| 滕州市| 三明市| 兴仁县| 友谊县| 郯城县| 鄂尔多斯市| 江油市| 梁平县| 屏山县| 响水县| 长阳| 万源市| 分宜县| 阿拉善盟| 南投市| 水城县| 嘉荫县| 石屏县| 咸阳市| 大新县| 大兴区| 浦县| 永修县|