Qt Stock Widget - Customizable Watchlist with Drag-and-Drop and Context Menu

Introduction

This article continues from our previous work on stock search functionality. Here we will explore how to implement a customizable watchlist for stocks, featuring drag-and-drop reordering and right-click context menus.

Note: This implementation does not include styling. For visual enhancements, CSS styles can be easily added.

The watchlist supports common operations like dragging items to reorder them, and right-clicking to access menu options such as moving items up/down, deleting, or setting priority.

Feature Overview

The implemented watchlist includes:

  • Search box supporting both stock codes and names
  • Preview panel with hover support and keyboard navigation
  • Drag-and-drop reordering with visual feedback during drag
  • Right-click context menu for item manipulation

Watchlist Implementation

We use QListWidget to build the watchlist, with each item containing a custom widget for displaying stock data.

1. List Initialization

Initialize the stock list with mock data for demonstration purposes. In a real application, this would come from a server API.

// Initialize the stock list
d_ptr->m_pStockList = new StockList;
connect(d_ptr->m_pStockList, &StockList::RowClicked, this, [this](const QString & symbol){
    emit RowClicked(symbol);
});

// Mock data for demo purposes
OptionalMarketItem item;
for (int i = 1; i <= 5; ++i)
{
    item.wstrSymbol = QString("0h000%1").arg(i).toStdWString();
    item.wstrName = QString("%1%1%1").arg(i).toStdWString();
    item.wstrIndustryName = QString("pingyin%1").arg(i).toStdWString();

    d_ptr->m_pStockList->AddItem(item);
}

2. Adding Items

To add an item, we create a standard QListWidgetItem and associate it with a custom widget that displays the stock ifnormation.

QListWidgetItem * StockList::AddItem(const OptionalMarketItem & data)
{
    ListItem * itemWidget = new ListItem;
    
    itemWidget->SetData(data);
    QListWidgetItem * item = new QListWidgetItem;
    addItem(item);
    item->setSizeHint(QSize(0, 50));

    setItemWidget(item, itemWidget);

    return item;
}

When stock data changes, we trigger UI updates by refreshing the style:

this->style()->unpolish(this);
this->style()->polish(this);

3. Context Menu

We override contextMenuEvent to provide a right-click menu with actions like delete, move up/down, and set priority.

void StockList::contextMenuEvent(QContextMenuEvent * event)
{
    if (d_ptr->m_AllowMenu == false)
    {
        return;
    }

    if (d_ptr->m_ContextMenu == nullptr)
    {
        d_ptr->m_ContextMenu = new QMenu(this);
        d_ptr->m_ContextMenu->setObjectName(QStringLiteral("StockListMenu"));
        d_ptr->m_ContextMenu->setFixedWidth(100);

        QAction * delAct = new QAction(QStringLiteral("Delete"), d_ptr->m_ContextMenu);
        QAction * topAct = new QAction(QStringLiteral("Move to Top"), d_ptr->m_ContextMenu);
        QAction * bottomAct = new QAction(QStringLiteral("Move to Bottom"), d_ptr->m_ContextMenu);
        QAction * upAct = new QAction(QStringLiteral("Move Up"), d_ptr->m_ContextMenu);
        QAction * downAct = new QAction(QStringLiteral("Move Down"), d_ptr->m_ContextMenu);

        connect(delAct, &QAction::triggered, this, &StockList::DeleteSotck);
        connect(topAct, &QAction::triggered, this, &StockList::TopSotck);
        connect(bottomAct, &QAction::triggered, this, &StockList::BottomSotck);
        connect(upAct, &QAction::triggered, this, &StockList::UpSotck);
        connect(downAct, &QAction::triggered, this, &StockList::DownSotck);

        d_ptr->m_ContextMenu->addAction(delAct);
        d_ptr->m_ContextMenu->addAction(topAct);
        d_ptr->m_ContextMenu->addAction(bottomAct);
        d_ptr->m_ContextMenu->addAction(upAct);
        d_ptr->m_ContextMenu->addAction(downAct);
    }
    d_ptr->m_ContextMenu->exec(mapToGlobal(event->pos()));

    QListWidget::contextMenuEvent(event);
}

4. Drag-and-Drop Functionality

We handle mouse events to simulate drag-and-drop behavior with visual cues:

  • mousePressEvent: Records initial state for drag detection
  • mouseMoveEvent: Handles drag movement and visual feedback
  • mouseReleaseEvent: Finalizes the drop operation
  • enterEvent/leaveEvent: Show/hide visual indicators

Key functions during drag:

// Initialization of visual elements
if (d_ptr->m_ShotLine == nullptr)
{
    InitShotLine();
}
if (d_ptr->m_ShotPicture == nullptr)
{
    InitShotLabel();
}

// Update cursor based on position
if (ListItem * newWidget = ItemWidget(d_ptr->dragItem))
{
    d_ptr->m_ShotPicture->move(QCursor::pos() - d_ptr->dragItemPos);
    d_ptr->m_DragRect = visualItemRect(d_ptr->dragItem);
    if (d_ptr->m_DragRect.contains(event->pos()) || event->pos().isNull())
    {
        if ((event->pos() - d_ptr->startPos).manhattanLength() > 5)
        {
            setCursor(Qt::ForbiddenCursor);
        }
    }
    else
    {
        setCursor(Qt::ArrowCursor);
    }
    if (d_ptr->m_ShotPicture->isHidden())
    {
        d_ptr->m_ShotPicture->show();
    }
}

5. Data Refresh

Refreshes the entire list with new data, adjusting rows as needed:

void StockList::Update_p(OptionalMarketItemVector data)
{
    d_ptr->m_bOnceLoad = true;
    disconnect(this, &QListWidget::currentItemChanged, this, &StockList::CurrentItemChanged);

    int i = 0;
    for (auto iter = data.begin(); iter != data.end(); ++iter, ++i)
    {
        bool success = false;
        if (QListWidgetItem * item = this->item(i))
        {
            if (ListItem * itemWidget = ItemWidget(item))
            {
                itemWidget->SetData(*iter);
                success = true;
            }
        }
        if (!success)
        {
            AddItem(*iter);
        }
    }

    if (i < this->count())
    {
        QListWidgetItem * item = nullptr;
        while (item = this->item(i))
        {
            if (ListItem * itemWidget = ItemWidget(item))
            {
                itemWidget->close();
                itemWidget = nullptr;
            }

            item = takeItem(i);
            delete item;
        }
    }

    if (d_ptr->m_LeftPress == false)
    {
        RecoveryCurrentItem();
    }

    connect(this, &QListWidget::currentItemChanged, this, &StockList::CurrentItemChanged);
}

Related Articles

  • Stock Market Dashboard Showcase
  • Custom Qt QLineEdit Context Menu
  • Stock Search Component with Keyboadr Navigation
  • Futu Niuniu Clone - Tab Dragging and Management
  • Futu Niuniu Clone - Magnetic Snap Layout
  • Futu Niuniu Clone - UI Styling
  • Futu Niuniu Clone - Advanced Clock Widget
  • Futu Niuniu Clone - Window Management
  • Futu Niuniu Clone - Layout Persistence

Tags: Qt stock watchlist drag-and-drop context-menu

Posted on Sun, 21 Jun 2026 16:39:19 +0000 by kamasheto