IT Notes

QSettings: Просматриваем, создаем и удаляем записи в реестре Windows

Для работы с реестром Windows можно воспользоваться программой regedit. Но может потребоваться реализовать более специализированный инструмент. Одним из возможных подходов является создание Qt-приложения, основанного на QSettings. Этим мы и займемся.

В первую очередь обеспечим возможность просмотра содержимого реестра:

registry-demo-start-thumbnail

Далее обеспечим возможность добавления новых значений в открытую ветку (с помощью кнопки Создать):

registry-demo-create-thumbnail

registry-demo-added-thumbnail

Наше приложение действительно сможет влиять на систему (поэтому будьте осторожны при тестировании примера):

environment-variables

приложение со ставками.

Также мы сможем удалять записи, которые нам не нужны (по нажатию кнопки Удалить):

registry-demo-selected-thumbnail

Предупреждение: приложение является демонстрационным, поэтому для упрощения реализации часть ошибок заведомо не обрабатывается и игнорируется. Например, кнопки Создать и Удалить действуют всегда, поэтому следите, чтобы состояние интерфейса отвечало логике действия (для создания - выделена ветка реестра, а для удаления - выбрано какое-нибудь значение), иначе в худшем случае это может привести к проблемам в работе системы.

Реализация

Заголовочный файл mainwidget.h:

#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include <QSet>

class QTreeWidgetItem;
class QSettings;

namespace Ui {
class MainWidget;
}

class MainWidget : public QWidget {
    Q_OBJECT

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

private slots:
    void onRegistryItemExpanded( QTreeWidgetItem* item );
    void onRegistryItemClicked( QTreeWidgetItem* item, int column );

    void onCreate();
    void onRemove();

private:
    void addRegistryGroup( QTreeWidgetItem* root, QSettings* settings );

    QString findPathForItem( QTreeWidgetItem* item ) const;

private:
    Ui::MainWidget *ui;
};

#endif // MAINWIDGET_H

Файл реализации mainwidget.cpp:

#include "mainwidget.h"
#include "ui_mainwidget.h"
#include "registryitemcreationdialog.h"

#include <QSettings>
#include <QDebug>
#include <QTreeWidgetItem>

#include <algorithm>

static const auto REG_PATH_SEPARATOR = "\\";

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

    auto* root = new QTreeWidgetItem( QStringList() << "Computer" );
    ui->treeWidget->addTopLevelItem( root );

    const auto REGISTRY_GROUPS = {
        "HKEY_CLASSES_ROOT",
        "HKEY_CURRENT_USER",
        "HKEY_LOCAL_MACHINE",
        "HKEY_USERS"
    };
    for( const auto& g : REGISTRY_GROUPS ) {
        QSettings settings( g, QSettings::NativeFormat );
        auto* item = new QTreeWidgetItem( QStringList() << g );
        root->addChild( item );
        addRegistryGroup( item, &settings );
    }

    ui->treeWidget->expandItem( root );

    connect(
        ui->treeWidget,
        SIGNAL( itemExpanded( QTreeWidgetItem* ) ),
        SLOT( onRegistryItemExpanded( QTreeWidgetItem* ) )
    );
    connect(
        ui->treeWidget,
        SIGNAL( itemClicked( QTreeWidgetItem*, int ) ),
        SLOT( onRegistryItemClicked( QTreeWidgetItem*, int ) )
    );

    connect( ui->bnCreate, SIGNAL( clicked( bool ) ), SLOT( onCreate() ) );
    connect( ui->bnRemove, SIGNAL( clicked( bool ) ), SLOT( onRemove() ) );
}

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

void MainWidget::onRegistryItemExpanded( QTreeWidgetItem* item ) {
    QSettings settings( findPathForItem( item ), QSettings::NativeFormat );
    addRegistryGroup( item, &settings );
}

void MainWidget::onRegistryItemClicked( QTreeWidgetItem* item, int column ) {
    Q_UNUSED( column )

    if( item && item->parent() ) {
        QSettings settings( findPathForItem( item ), QSettings::NativeFormat );
        ui->tableWidget->setRowCount( 0 );
        ui->tableWidget->clearContents();
        for( const auto& key : settings.childKeys() ) {
            auto row = ui->tableWidget->rowCount();
            ui->tableWidget->insertRow( row );

            auto item = new QTableWidgetItem( key );
            item->setFlags( item->flags() & ~Qt::ItemIsEditable );
            ui->tableWidget->setItem( row, 0, item );

            auto val = settings.value( key ).toString();
            item = new QTableWidgetItem( val );
            item->setFlags( item->flags() & ~Qt::ItemIsEditable );
            ui->tableWidget->setItem( row, 1, item );
        }
    }
}

void MainWidget::onCreate() {
    RegistryItemCreationDialog dlg( this );
    if( dlg.exec() == QDialog::Accepted ) {
        auto item = ui->treeWidget->currentItem();
        QSettings settings( findPathForItem( item ), QSettings::NativeFormat );
        settings.setValue( dlg.getName(), dlg.getValue() );
        settings.sync();
        onRegistryItemClicked( item, 0 );
    }
}

void MainWidget::onRemove() {
    auto item = ui->treeWidget->currentItem();
    auto path = findPathForItem( item );
    QSettings settings( findPathForItem( item ), QSettings::NativeFormat );
    settings.remove( ui->tableWidget->item( ui->tableWidget->currentRow(), 0 )->text() );
    settings.sync();
    onRegistryItemClicked( item, 0 );
}

void MainWidget::addRegistryGroup( QTreeWidgetItem* root, QSettings* settings ) {
    static int depth = 0;
    static const int MAX_DEPTH = 2;

    ++depth;

    if( 0 < root->childCount() ) {
        for( int i = 0; i < root->childCount(); ++i ) {
            auto child = root->child( i );
            if( 0 < child->childCount() ) {
                break;
            }

            settings->beginGroup( child->text( 0 ) );
            addRegistryGroup( child, settings );
            settings->endGroup();
        }
    } else if( depth <= MAX_DEPTH ) {
        for( const auto& g : settings->childGroups() ) {
            auto* item = new QTreeWidgetItem( QStringList() << g );
            root->addChild( item );

            settings->beginGroup( g );
            addRegistryGroup( item, settings );
            settings->endGroup();
        }
    }

    --depth;
}

QString MainWidget::findPathForItem( QTreeWidgetItem* item ) const {
    QStringList reversePath;
    for( ; item != nullptr; item = item->parent() ) {
        reversePath << item->text( 0 );
    }

    reversePath.pop_back();
    std::reverse( reversePath.begin(), reversePath.end() );

    return reversePath.join( REG_PATH_SEPARATOR );
}

Просмотр реестра мы основываем на классе QSettings с его функциями-членами для доступа к группам (childGroups()) и к ключам для заданной группы (childKeys()). При этом QSettings инициализируется следующим образом:

QSettings settings( groupName, QSettings::NativeFormat ); // где groupName, например, - "HKEY_CURRENT_USER"

Поскольку реестр содержит очень много записей, мы не можем загрузить и отображать его целиком. Поэтому приходится идти на небольшую хитрость - загружаем древовидную структуру реестра не больше, чем на один уровень ниже видимого:

void MainWidget::addRegistryGroup( QTreeWidgetItem* root, QSettings* settings ) {
    static int depth = 0;
    static const int MAX_DEPTH = 2;

    ++depth;

    if( 0 < root->childCount() ) {
        for( int i = 0; i < root->childCount(); ++i ) {
            auto child = root->child( i );
            if( 0 < child->childCount() ) {
                break;
            }

            settings->beginGroup( child->text( 0 ) );
            addRegistryGroup( child, settings );
            settings->endGroup();
        }
    } else if( depth <= MAX_DEPTH ) {
        for( const auto& g : settings->childGroups() ) {
            auto* item = new QTreeWidgetItem( QStringList() << g );
            root->addChild( item );

            settings->beginGroup( g );
            addRegistryGroup( item, settings );
            settings->endGroup();
        }
    }

    --depth;
}

При этом нам приходится обрабатывать сигнал разворачивания элемента дерева:

void MainWidget::onRegistryItemExpanded( QTreeWidgetItem* item ) {
    QSettings settings( findPathForItem( item ), QSettings::NativeFormat );
    addRegistryGroup( item, &settings );
}

Поиск пути в реестре для некоторого элемента дерева происходит с помощью вспомогательной функции:

QString MainWidget::findPathForItem( QTreeWidgetItem* item ) const {
    QStringList reversePath;
    for( ; item != nullptr; item = item->parent() ) {
        reversePath << item->text( 0 );
    }

    reversePath.pop_back();
    std::reverse( reversePath.begin(), reversePath.end() );

    return reversePath.join( REG_PATH_SEPARATOR );
}

Обратите внимание, что вложенные элементы в пути реестра разделяются символами "\\" (например, HKEY_CURRENT_USER\\Environment).

Чтение значений происходит при щелчке на элементе в дереве:

void MainWidget::onRegistryItemClicked( QTreeWidgetItem* item, int column ) {
    Q_UNUSED( column )

    if( item && item->parent() ) {
        QSettings settings( findPathForItem( item ), QSettings::NativeFormat );
        ui->tableWidget->setRowCount( 0 );
        ui->tableWidget->clearContents();
        for( const auto& key : settings.childKeys() ) {
            auto row = ui->tableWidget->rowCount();
            ui->tableWidget->insertRow( row );

            auto item = new QTableWidgetItem( key );
            item->setFlags( item->flags() & ~Qt::ItemIsEditable );
            ui->tableWidget->setItem( row, 0, item );

            auto val = settings.value( key ).toString();
            item = new QTableWidgetItem( val );
            item->setFlags( item->flags() & ~Qt::ItemIsEditable );
            ui->tableWidget->setItem( row, 1, item );
        }
    }
}

Обратите внимание, что для упрощения мы считываем все значения без учета их типа, как текст. Это может привести к появлению загадочных надписей в интерфейсе программы на неизвестных человечеству языках. Именно по этой причине я не добавил функцию редактирования значений в нашу программу.

Создание новой записи сводится к добавлению значения в текущую ветку реестра:

void MainWidget::onCreate() {
    RegistryItemCreationDialog dlg( this );
    if( dlg.exec() == QDialog::Accepted ) {
        auto item = ui->treeWidget->currentItem();
        QSettings settings( findPathForItem( item ), QSettings::NativeFormat );
        settings.setValue( dlg.getName(), dlg.getValue() );
        settings.sync();
        onRegistryItemClicked( item, 0 );
    }
}

Запрос данных у пользователя осуществляем с помощью модального диалогового окна, описание которого здесь мы опускаем.

Удаление значения из реестра сводится реализуется не сложнее, чем добавление:

void MainWidget::onRemove() {
    auto item = ui->treeWidget->currentItem();
    auto path = findPathForItem( item );
    QSettings settings( findPathForItem( item ), QSettings::NativeFormat );
    settings.remove( ui->tableWidget->item( ui->tableWidget->currentRow(), 0 )->text() );
    settings.sync();
    onRegistryItemClicked( item, 0 );
}

Исходники

 Скачать пример работы с реестром Windows с помощью QSettings

Похожие публикации