Managing Multiple Floating Windows in Financial UI Applications

System Overview

In complex financial trading applications, developers often face the challenge of managing numerous dynamic floating windows. Typically, users access these windows through a main dashboard containing various tool buttons. Clicking a button spawns a floating window. While these windows share common behavioral traits—such as dragging, resizing, and border highlighting—their internal content and specific menu interactions vary significantly.

Key requirements for such a system include:

  • Draggable title bars.
  • Resizable borders with mouse interactions.
  • Border highlighting upon selection.
  • Title bars containing names, close buttons, and context-specific menus.
  • Interaction between the title bar and the central content area.
  • Automatic positioning to prevent new windows from perfectly overlapping existing ones of the same type.

This article explores a component-based architecture designed to manage these floating windows efficiently, focusing on the "Mini Quote" widget as a case study.

Architectural Design

When designing component-based features, the primary goal is to maximize reusability and minimize the effort required for other developers to implement new business modules. The core principle is to separate the generic "shell" of the window (title bar, borders, resizing logic) from the specific "content" (business logic, data display).

Component Separation

The floating window can be divided into two distinct parts:

1. The Window Shell

Every floating window requires a standardized frame that handles movement and resizing. This shell is abstracted into a reusable base class. Developers creating new widgets (like a stock watchlist or a timer) do not need to reimplement these standard window mechanics.

However, the shell is not entirely static. The buttons and menus on the title bar must adapt based on the content window. To facilitate this, we define a communication interface.

2. The Content Area

This area displays the actual business data. Content windows generally fall into two categories:

  • Interactive: Implements a specific interface to communicate with the shell (handling menu clicks, providing data for display modes).
  • Static: purely for display, though in complex financial apps, most windows are interactive.

Key Class Implementations

The following diagram illustrates the relationships between the core classes involved in this architecture.

This abstract class acts as the bridge between the shell and the content. It defines the contract that all content windows must adhere to, allowing the shell to query data and send notifications without knowing the concrete implementation.


/**
 * @brief Interface for communication between the Window Shell and Content.
 */
class IWindowClient {
public:
    virtual ~IWindowClient() = default;

    // Defines the type of view/menu to display based on the tool type
    virtual ViewMode getDisplayMode(ToolType type) = 0;

    // Provides data items for menus or display configuration
    virtual void getMenuItems(QVector<MenuItem>& items) = 0;

    // Returns the preferred size for the content
    virtual QSize getPreferredSize() const = 0;

    // Handles notifications sent from the Shell (e.g., menu clicks)
    virtual void onNotification(const QString& message) = 0;
};

The shell calls getMenuItems to populate the title bar menu and onNotification to relay user actions back to the content.

  1. StockQuoteWidget (The Content)

This class represents the specific business logic for the "Mini Quote" feature. It inherits from QWidget and implements IWindowClient.

Here is how it provides data for the shell's menu:


void StockQuoteWidget::getMenuItems(QVector<MenuItem>& items)
{
    // Generate dummy data for demonstration
    for (int i = 0; i < 9; ++i) {
        MenuItem item;
        item.label = QChar('A' + i);
        item.id = item.label;
        item.iconPath = ":/icons/tool_btn_normal.png";
        items.append(item);
    }
}

  1. WindowContainer (The Shell)

This class serves as the generic wrapper for all floating windows. It contains the customized title bar, handles mouse events for dragging and resizing, and hosts the central content widget.

When the user interacts with the title bar (e.g., clicks a custom menu button), the WindowContainer emits a signal that is processed by the mediater/context class.

  1. WindowMediator (The Context)

To avoid tight coupling between the generic shell and specific content, a WindowMediator is introduced. It manages the mapping between shell instances and their content interfaces.

Registration process:


void WindowMediator::registerWidget(WindowContainer* shell, IWindowClient* client)
{
    m_widgetMap[shell] = client;

    // Connect shell signals to the mediator's slot
    connect(shell, &WindowContainer::buttonClicked, 
            this, &WindowMediator::processAction);
}

Event handling logic:


void WindowMediator::processAction(int type, const QPoint& globalPos)
{
    auto* shell = qobject_cast<WindowContainer*>(sender());
    if (!shell || !m_widgetMap.contains(shell)) return;

    auto* client = m_widgetMap[shell];
    ViewMode mode = client->getDisplayMode(static_cast<ToolType>(type));

    if (mode == ViewMode::CONFIGURATION) {
        showConfigurationPanel(shell, client, globalPos);
    }
}

  1. WidgetFactory (Creation and Positioning)

The factory class is responsible for constructing the WindowContainer and initializing the specific content widget. This centralizes the creation logic.

Furthermore, the factory manages the initial position of new windows to prevent them from covering identical windows completely. It maintains an offset vector that increments with each creation of the same type.


WindowContainer* WidgetFactory::createWidget(WidgetType type, QWidget* parentPanel)
{
    // 1. Create the concrete content
    IWindowClient* content = nullptr;
    if (type == WidgetType::MINI_QUOTE) {
        content = new StockQuoteWidget();
    }
    // ... other types

    // 2. Create the shell and bind content
    auto* container = new WindowContainer(content, parentPanel);
    m_mediator.registerWidget(container, content);

    // 3. Calculate position with offset
    QPoint pos = calculateNextPosition(type, parentPanel);
    container->move(pos);
    
    return container;
}

void WidgetFactory::updatePositionOffset(bool reset, QWidget* parent)
{
    const int step = 20;

    if (reset) {
        m_currentOffset += QPoint(step, step);
    } else {
        m_currentOffset = QPoint(0, 0);
    }

    QRect bounds = parent->rect();
    // Reset if the offset moves the window too far
    if (m_currentOffset.y() > bounds.height() / 4 || 
        m_currentOffset.x() > bounds.width() / 4) {
        m_currentOffset = QPoint(-step, -step);
    }
}

This logic ensures that if a user clicks the "Mini Quote" button three times, the three resulting windows will cascade diagonally, ensuring all are visible.

Posted on Fri, 18 Sep 2026 16:48:44 +0000 by activeserver