Последнее обновление:
July 3, 2020

Есть мысль... Жми, напиши!
Что имеем: Постов : 140 Авторов: 1 Категорий: 22

Qt QCoreApplication или EventLoop в DLL

Потребовалось мне создать динамическую dll для сторонней программы (не Qt).
Разумеется, хотелось использовать все возможности Qt, а именно сигналы/слоты/QTCPSocket и прочее.

Но для их работы нужна очередь событий (EventLoop), то есть либо QCoreApplication, либо QEventLoop. Но в dll этого нет.

Можно, конечно, сигналы-слоты использовать с Qt::DirectConnect (прямой вызов), но тогда всё равно не будут нормально работать QTCPSocket (потому что для их работы в WndProс должно приходить уведомление), поэтому придётся вызывать методы waitForBytesWriten() и т.д.) и прочее, кто работает асинхронно и ждёт событий от системы.

Была бы программа, в которую подключается dll сама написана на Qt, то таких танцев с бубном не потребовалось (т.к. там уже есть QApplication и ещё одного не нужно)

В dll основная функция, с которой всё начинается, это DllMain (при загрузке/выгрузке в процесс или подключении/отключении к потоку она автоматически вызывается системой).

extern "C" BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch(fdwReason) {
        case DLL_PROCESS_ATTACH: 
          QCoreApplication app(argc, nullptr); //-- Нельзя так!
          ...
          app.exec(); //-- Дальше загрузка остальных библиотек и хост-программа целиком зависнет, т.к. exec() запускает почти бесконечный цикл и из DllMain не выйдем.
        break;

        case DLL_PROCESS_DETACH: 
          ...
        break;
    }

    return true;
}

Кажется, что достаточно поместить QCoreApplication app(argc, nullptr); в блок DLL_PROCESS_ATTACH, но тогда загрузка всех остальных библиотек зависнет наглухо, про это чётко сказано в официальной документации: https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain

Что делать? Самым простым способом, это будет создать новый поток (но в документации прописано, что это вроде не есть гуд, но не понятно почему) и запустить его на выполнение, а внутри него уже можно запускать бесконечный цикл. Сделаем функцию, которую поток запустит при запуске и сделаем запуск потока.

/**
* @brief В отдельном потоке
*/
DWORD WINAPI run(HMODULE hInst)
{
    int argC=0;
    QCoreApplication app(argC, nullptr);
    ... //-- Можем использовать все возможности Qt
    return app.exec(); //-- Бесконечный цикл запущен, пока не завершим, из этой функции не выйдем
}

/**
* @brief При загрузке библиотеки в процесс
*/
void init(HINSTANCE hInst)
{
    toLog("INIT\n");
    CreateThread(nullptr, 0,  (LPTHREAD_START_ROUTINE)run, (LPVOID)hInst, 0, nullptr);
}

/**
* @brief При выгрузке из процесса
*/
void deInit(HINSTANCE hInst)
{    
   ...
}

extern "C" BOOL __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    Q_UNUSED(lpvReserved);

    switch(fdwReason) {
        case DLL_PROCESS_ATTACH: init(hinstDLL); break;
        case DLL_PROCESS_DETACH: deInit(hinstDLL); break;
    }

    return true;
}
CONFIG += qt

И вроде бы всё хорошо, сигналы начнут ходить, слоты начнут вызываться.

ВАЖНО:
1. Почему используется winapi функция создания потока CreateThread()?
Потому, что при завершении программы система сама убъёт поток.
В случае, к примеру, QtConcurrent::run(..) нам придётся самостоятельно отслеживать закрытие всех окон и делать выход из QCoreApplicaton. Если всё же пойдёте этим путём, учтите, что напрямую из другого потока вызывать будет бесполезно! Придётся делать через «postMessage» (т.к. sendMessage сразу вызывает) или флаг ставить, и по таймеру обрабатывать.

2. Кстати, так же внутри классов нельзя вызывать системные функции, которые могут заблокировать поток Qt. К примеру, winapi MessageBoxW(nullptr, ….)! Как избежать?
Первым параметром идёт указатель на окно, которое будет принимать события (а следовательно и поток) (точнее, обработкой то занимается WndProc), нам всего лишь нужно поставить туда указатель на окно от QCoreApplication. Да, Вы можете возразить, что его нет! На самом деле есть, просто скрытое. Найти его можно с помощью winapi FindWindowEx по id процесса (можно из из Qt достать, но создатели вроде бы не предусмотрели таких извращений, поэтому без модификации файлов фреймворка не получится).
ИД процесса (не путить с ИД процесса приложения!) можно получить: QCoreApplication::applicationPid().
Чуть ниже выложу реализацию, запускать так:
HWND hApp = findwind(nullptr, L»», L»», 1, QCoreApplication::applicationPid())

3. Что бы библиотека загрузилась, ей для работы нужны Qt шные библиотеки (той же версии, которой компилируется текущая), это как минимум:
1. libwinpthread-1.dll
2. libgcc_s_dw2-1.dll
3. libstdc++-6.dll
4. Qt5Core.dll
5. Остальные

Порядок загрузки важен!
Положите либо рядом с загружаемой, либо в System32 (в случае Винды).
Либо при загрузке библиотеки используйте loadLibraryEx которая сама подгрузит нужные (в случае, если Вы используете injectdll (инъекцию dll)).

P.S. Функции вывода в лог (принцип такой же, как и у printf, не забудьте заголовочники подключить):

void toLog(const char *fmt, ...)
{        
    va_list arg;
    va_start(arg, fmt);
    char buffer[4096];
    int rc = vsnprintf(buffer, sizeof(buffer), fmt, arg);
    va_end(arg);

    FILE * fp = fopen("C:/DELME/Log.txt", "a");
    fprintf(fp, buffer);
    fclose(fp);
}

void toLogW(const wchar_t *fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    wchar_t buffer[4096];
    int rc = vsnwprintf(buffer, sizeof(buffer), fmt, arg);
    va_end(arg);

    FILE * fp = fopen("C:/DELME/Log.txt", "a");
    fwprintf(fp, buffer);
    fclose(fp);
}

Поиск окна по нескольким параметрам:

HWND findwind(HWND parent, std::wstring text, std::wstring className, int instance = 1, DWORD processID=0, bool fromStart=true)
{
    WCHAR bufferText[1024];
    WCHAR bufferClass[1024];
    DWORD dwPID = 0;
    HWND hWnd = parent;

    while (true) {
        hWnd = FindWindowEx(nullptr, hWnd, nullptr, nullptr);

        if ( hWnd==nullptr ) break;
        if ( !IsWindow(hWnd) ) { continue; }

        if( !text.empty() ) {
            GetWindowText(hWnd, bufferText, 1000);
            wchar_t * pos = wcsstr(bufferText, text.c_str());
            if ( (pos==nullptr) || (fromStart && pos-bufferText!=0) ) { continue; }
        }

        if ( processID>0) {
            GetWindowThreadProcessId(hWnd, &dwPID);
            if ( dwPID!=processID ) { continue; }
        }

        if ( !className.empty() ) {
            GetClassName(hWnd, bufferClass, 1000);
            if ( wcsstr(bufferClass, className.c_str())-bufferClass!=0 ) { continue; }
        }

        if ( --instance==0 ) { return hWnd; }
    }

    return nullptr;
}

P.P.S. Так же бесконечный цикл/очередь событий нужна, если ставиться SetWindowHookEx, иначе без этого он просто сброситься и хук вызываться не будет. Это же применимо и для injectDLL.
Кстати, при срабатывании хука напрямую Qt слоты вызывать не стоит, т.к. хук в другом потоке. Лучше использовать «QCoreApplication::postEvent(…)» а в обработчике переопределить функцию bool event(QEvent * event) override; Вот так примерно:

#include <QEvent>
#include "minwindef.h"
#include "winuser.h"
/**
* @brief Тип события для обработки хука
*/
const QEvent::Type WndProcHookEventType = static_cast<QEvent::Type>(QEvent::User+1000);
class WndProcHookEvent: public QEvent
{
public:
    WndProcHookEvent(WPARAM wParam, LPARAM lParam): QEvent(WndProcHookEventType) {
        _cmd = wParam;
        //-- Нужно сразу получать значения всего интересующего, иначе указатели после выхода их хука могут стать не валидными
        _cwps = *reinterpret_cast<CWPSTRUCT*>(lParam); 
    }

    uint cmd() const { return _cmd; }
    CWPSTRUCT cwps() { return _cwps; }

private:
    uint _cmd;
    CWPSTRUCT _cwps;

};

void run(HMODULE hInst)
{
   ...
   hHook = SetWindowsHookExW(WH_CALLWNDPROC, hookCB, 0, GetWindowThreadProcessId(<base program window handle>, nullptr));
   ...
}

LRESULT WINAPI hookCB(int nCode, WPARAM wParam, LPARAM lParam)
{
    if( nCode<0 ) {
        CallNextHookEx(hHook, nCode, wParam, lParam );
        return 0;
    }

    QCoreApplication::postEvent(_handler, new WndProcHookEvent(wParam, lParam));

    return CallNextHookEx(hHook, nCode, wParam, lParam);
}

bool event(QEvent* event) override 
{
   if ( event->type()!=WndProcHookEventType ) { return false; }
   CWPSTRUCT cwps = wndProc->cwps();
   if ( cwps.message==WM_COMMAND ) {
      ...
   }
   return true;
}

Вот как-то так.

Views :

11

Qt, QQuickPaintedItem отрисовка в отрицательных координатах (paint outside bounds).

Потребовалась мне тут на днях отрисовка сложных графиков в QML.
Увы, но возможностей существующих графиков не хватило — пришлось изобретать свои.

Проблема возникла с тем, что по умолчанию всё, что имеет отрицательные координаты x и y будет подвергнуто кастрации.
Да же если поставим:

setClip(false);
setFlag(QQuickItem::ItemClipsChildrenToShape, false);

И попробуем вывести круг:

void MyCircle::paint(QPainter * painter)
{
   QPainterPath path;
   path.addEllipse(-200, -200, 400, 400);

   painter->setPen(Qt::NoPen);
   painter->fillPath(path, QBrush(QColor("orange")));
}

Всё, что меньше (0, 0) будет обрезано:

Можно было бы задать размер по размеру основного окна, но мне нужно было чтобы итем холста имел конкретные размеры и положение, т.к. он на сцене будет не один.

Самое простое решение на данный момент — задать QPainter глобальную матрицу трансформации со смещением в противоположную сторону (что бы не высчитывать новые координаты при отрисовке примитивов). А компоненту задать соответственно в координатах X и Y это же значение смещения. Единственный минус — придётся делать обёртку над компонентом.
Вот так примерно:

painter->setWorldTransform(QTransform::fromTranslate(200, 200), true);
setPosition(QPoint(-200, -200));

И в QML соответственно обёртку сделать:

import QtQuick 2.12
import QtQuick.Window 2.12
import MyCircle 1.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QQuickPaintedItem outside bounds")

    Rectangle {
        anchors.fill: parent
        color: "black"
    }

    Item {
        id: myCircleWrapper
        anchors.centerIn: parent
        width: 200
        height: 200

        MyCircle {
            id: myCircle
            width: parent.width
            height: parent.height
            clip: false
        }
    }
}

И результат:

Как сделать более правильно я, к сожалению, после нескольких дней раздумий и ковырянии исходников Qt не придумал…

Views :

26

Использование интерфейсов классов в Qt и QML

Привет!

Порою удобнее в QML работать именно с интерфейсом класса, а так же иметь возможность засунуть его в QVariant.  

Разумеется простым способом в «лоб» не получится, т.к. Qt в QML работает с QObject, а мы от него не унаследовались и никакой информации для метасистемы не дали.

Долго я копался в недрах метасистемы Qt, уж собирался делать костыли, но наткнулся на макрос Q_DECLARE_INTERFACE.

И так, допустим мы хотим сделать интерфейс класса для работы с одним свойством myProperty, тем самым заставив определить функции получения, установки значения и сигнала (который сам по себе то же функция) об изменении значения. 

Пишем:

#ifndef IINTERFACE_H
#define IINTERFACE_H

#include <QObject>


class IInterface {
public:
    virtual QString myProperty() const =0;
    virtual void setMyProperty(QString myProperty) =0;
    virtual void myPropertyChanged(QString myProperty) =0;
};

Q_DECLARE_INTERFACE(IInterface, "pavelk.iinterface")


#endif // IINTERFACE_H

Обратите внимание на строку с «Q_DECLARE_INTERFACE» — тем самым мы даём понять метаобъектной системе Qt, что это интерфейс, что бы он прописал необходимые функции по получению исходного класса.

Ну и сам класс:

#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>

#include "iinterface.h"

class MyClass : public QObject, public IInterface
{
    Q_OBJECT
    Q_INTERFACES(IInterface)
    Q_PROPERTY(QString myProperty READ myProperty WRITE setMyProperty NOTIFY myPropertyChanged)
public:
    explicit MyClass(QObject *parent = nullptr);

    QString myProperty() const;

signals:
    void myPropertyChanged(QString myProperty);

public slots:
    void setMyProperty(QString myProperty);

private:
    QString m_myProperty;
};

#endif // MYCLASS_H

Обратите внимание на строку с «Q_INTERFACES(IInterface)» — тем самым мы даём знать Qt как именно преобразовывать этот класс к указанному интерфейсу. Кстати, можно наследоваться сразу от нескольких интерфейсов — просто пропишите через пробел их все.

Ну и теперь самое интересное. Преобразовываем класс к интерфейсу и передаём этот интерфейс в QML

   MyClass * myClass = new MyClass(0);
   IInterface * interface = myClass;
   engine.rootObjects().at(0)->setProperty("myClassInterface", qVariantFromValue( dynamic_cast<QObject*>(interface) ));

Напрямую в QVariant интерфейс засовывать нельзя, т.к. он не знает необходимой информации, поэтому преобразовываем сначала в QObject*, а благодаря тому, что мы прописал Q_DECLARE_INTERFACE метасистема знает как работать с этим интерфейсом.

Полный код примера тут:  https://github.com/Riflio/QMLInterfaces

Вот как-то так =)

Views :

676

Qt, QEventLoop and connect/disconnect lambda function

Приветствую!

Кому некогда, можно сразу прыгнуть в конец к итогу.

Бывают ситуации, когда нужно синхронно дождаться завершения асинхронного действия, при этом не подвешивая основной поток (например, не продолжать выполнение функции, пока ответ в QTCPSocket onReadyRead от сервера не придёт).   

В нашем случае для примера давайте подождём с выполнением функции, пока таймер не досчитает до 5. 

Делаем основу:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QGuiApplication>
#include <QDebug>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз

    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";
        _timer->start(5000); //-- 5 секунд в миллисекундах


        qDebug()<<"Timer END";
    }

signals:

public slots:

private:
    QTimer * _timer;
};

#endif // APPCORE_H

Но если мы запустим так, то вывод  «Timer END» произойдёт без какой-либо задержки, т.к. таймер ведёт отсчёт асинхронно в другом потоке.

Решением в «лоб» было бы подписаться на событие срабатывания таймера, объявить флаг срабатывания и в бесконечном цикле отслеживать его, как-то так, например:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
        connect(_timer, &QTimer::timeout, this, &AppCore::onTimeOut);

    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";
        _timer->start(5000); //-- 5 секунд в миллисекундах

        while (true) {
            if (_timeOut) {
                break;
            }
            QGuiApplication::processEvents();
        }

        qDebug()<<"Timer END";
    }

public slots:
    void onTimeOut()
    {
        _timeOut = true;
    }

private:
    QTimer * _timer;
    bool _timeOut=false;
};

#endif // APPCORE_H

Кстати, если бы мы не указали «QGuiApplication::processEvents();«, то слот «onTimeOut()» не вызвался бы никогда, т.к. цикл у нас бесконечный, а так мы заставляем всё таки обработать события, а заодно и не подвешивать сильно интерфейс. 

Но решение это слишком топорное и не красивое. Что бы не использовать бесконечные циклы у Qt есть QEventLoop.  Выполнение функции приостанавливается методом «exec()» и QEventLoop ждёт, пока не будет вызван метод «exit()» и лишь потом продолжается.

Делаем:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QEventLoop>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
        connect(_timer, &QTimer::timeout, this, &AppCore::onTimeOut);

    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";

        _timer->start(5000); //-- 5 секунд в миллисекундах
        _loop.exec(); //-- Ждём, пока будет вызван exit();

        qDebug()<<"Timer END";
    }

public slots:
    void onTimeOut()
    {
        _loop.exit();
    }

private:
    QTimer * _timer;
    QEventLoop _loop;
};

#endif // APPCORE_H

Стало чуть-чуть красивее, но у нас всё ещё висит одноразовый слот «onTimeOut()» и одноразовая переменная «_loop«.  Это сейчас она одна, а если в нашем классе нужно в 5 разных местах дожидаться ответов? Как-то по 5 одноразовых слотов и переменных иметь некрасиво…

Благо в Qt начиная с 5 версии появилась возможность при соединении сигнал-слота вместо слота использовать лямбда-функцию, этой фишкой мы и воспользуемся, что бы избавиться он объявления глобального слота «onTimeOut()» и переменной «_loop»

Делаем: 

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QEventLoop>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
    }

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";

        QEventLoop _loop;

        connect(_timer, &QTimer::timeout, [&](){
            _loop.exit();
        });

        _timer->start(5000); //-- 5 секунд в миллисекундах
        _loop.exec(); //-- Ждём, пока будет вызван exit();

        qDebug()<<"Timer END";
    }

private:
    QTimer * _timer;

};

#endif // APPCORE_H

Запускаем и ровно через 5 секунд после «Timer BEGIN» у нас выведется «Timer END»  и вроде бы добились чего хотели, но тут есть одна тонкость. Для наглядности я в лямбду добавлю вывод информации о срабатывании, вот так теперь она выглядит::

connect(_timer, &QTimer::timeout, [&_loop](){
    qDebug()<<"TIME OUT!";
    _loop.exit();
});

Допустим нам нужно в нескольких местах ждать ответа, и мы два раза вызываем функцию:

    ....
    waitFunction();
    waitFunction();
    ....

Вывод будет такой:

Timer BEGIN
TIME OUT!
Timer END
<br>Timer BEGIN
TIME OUT!
TIME OUT!
Timer END

Как видите, «TIME OUT» после второго вызова вывелось два раза! А если бы мы функцию «waitFunction()» вызвали 10 раз, то соответственно «TIME OUT» вывелось бы то же 10 раз подряд.  Так явно не должно быть! В чём дело?  А дело в том, что лямбда функция автоматически не отключается! Если забыть про эту фишку можно нарваться в том числе и на «SIGSEGV Segmentation fault», сегфолт короче. 

Решение — это не забывать отключать (disconnect signal lambda) сигнал от лямбды, но просто так это сделать не получится, т.к. это всё таки лямбда, нужно запоминать информацию, которую возвращает метод «QMetaObject::Connection conn = connet(….)«, а по ней отключать «disconnect(conn)«. 

Делаем:

void waitFunction()
{
    qDebug()<<"Timer BEGIN";

    QEventLoop _loop;

    QMetaObject::Connection conn = connect(_timer, &QTimer::timeout, [&_loop](){
        qDebug()<<"TIME OUT!";
        _loop.exit();
    });

    _timer->start(5000); //-- 5 секунд в миллисекундах
    _loop.exec(); //-- Ждём, пока будет вызван exit();

    disconnect(conn);

    qDebug()<<"Timer END";
}

Срабатывать будет  один раз и отключаться, вывод придёт в норму:

Timer BEGIN
TIME OUT!
Timer END

Timer BEGIN
TIME OUT!
Timer END

Но об  «disconnect(….)»  можно забыть, поэтому предлагаю использовать умные указатели, а именно QSharedPointer, но у него нужно не забыть реализовать отключение, т.к. сам по себе он это делать не умеет, а так как писанины получается многовато, поэтому предлагаю запилить макрос.

Итоговый код:

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QGuiApplication>
#include <QEventLoop>
#include <QSharedPointer>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = nullptr): QObject(parent)
    {
        _timer = new QTimer(this);
        _timer->setSingleShot(true); //-- Срабатывать только один раз
    }

    #define AutoDisconnect(l) \
        QSharedPointer<QMetaObject::Connection> l = QSharedPointer<QMetaObject::Connection>(\
            new QMetaObject::Connection(), \
            [](QMetaObject::Connection * conn) { /*QSharedPointer сам по себе не производит отключения при удалении*/ \
                QObject::disconnect(*conn);\
            }\
        ); *l //-- Use AutoDisconnect(conn1) = connect(....);

    void waitFunction()
    {
        qDebug()<<"Timer BEGIN";

        QEventLoop _loop;

        AutoDisconnect(conn) = connect(_timer, &QTimer::timeout, [&_loop](){
            qDebug()<<"TIME OUT!";
            _loop.exit();
        });

        _timer->start(5000); //-- 5 секунд в миллисекундах
        _loop.exec(); //-- Ждём, пока будет вызван exit();

        qDebug()<<"Timer END";
    }

private:
    QTimer * _timer;

};

#endif // APPCORE_H

Вот как-то так =)

P.S. Макросы не зло, нужно просто уметь их готовить 😉

Views :

709

Layout.fillWidth: true и Layout.preferredWidth/Layout.minimumWidth зависимость (очередная хитрость)

Сталкиваюсь иногда с некоторыми хитростями в QML, о которых, по всей видимости, приходится только догадываться, ибо то ли я проглядел это в документации, то ли этого действительно в ней не указано.

Так вот, задача: нужно три колонки одинаковой ширины.

Делаем:

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Layouts 1.3

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("QMLTips&Tricks24")

    RowLayout {
        anchors.fill: parent
        spacing: 0

        Rectangle {
            color: "blue"
            Layout.fillHeight: true
            Layout.fillWidth: true            
        }

        Rectangle {
            color: "orange"
            Layout.fillHeight: true
            Layout.fillWidth: true
        }


        Rectangle{
            color: "green"
            Layout.fillHeight: true
            Layout.fillWidth: true            
        }

    }
}

Соответственно на выходе получаем:

Получилось как и задумывали.

Обновим задачу:  Оранжевая колонка должна иметь предпочтительную ширину 200 пикселей.
Делаем:

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Получаем:

Что-то не то, да..? 

Вот тут начинается самое интересное.

Дело в том, что Layout.prefferedWidth (а так же Layout.minimumWidth) управляет пропорционально шириной относительно соседей, когда задано Layout.fillWidth: true

Соответственно что бы поведение пришло в норму, мы должны у соседей  — синего и зелёного прямоугольника то же прописать желаемую ширину.

Делаем:

Rectangle {
    color: "blue"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle{
    color: "green"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Получаем:

То есть как и задумывали, три одинаковые колонки.

Теперь наглядный пример насчёт пропорционально соседям изменяемости размеров. Если мы у синего прямоугольника зададим желаемую ширину в 50, а у зелёного 100, то соответственно синий будет в 4 раза меньше оранжевого (200/50=4), а зелёный буде в два раза меньше оранжевого (200/100=2). 

То есть родитель Layout (RowLayout/ColumnLayout/GridLayout) берёт от потомков наибольший желаемый размер и относительно него пропорционально выставляет ширину всем потомкам.
Повторюсь, что так QML себя ведёт, только когда задано Layout.fillWidth: true

Делаем:

Rectangle {
    color: "blue"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 50
}

Rectangle {
    color: "orange"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 200
}

Rectangle{
    color: "green"
    Layout.fillHeight: true
    Layout.fillWidth: true
    Layout.preferredWidth: 100
}

Получаем:

Короче, считаем, что при указании preferredWidth, minimumWidth мы работаем с пропорциями относительно остальных в одном контейнере и всё. 

Почему именно так, а не иначе?  

По-моему как раз для того, что бы можно было задать поведение при растягивании/сжимании…

 

Аналогичная ситуация произойдёт, если мы оранжевой колонке захотим указать minimumWidth: 100 при fillWidth:true

Что делать, если нужно, что бы все колонки имели одинаковую ширину и заполняли всё пространство (т.е. fillWidth: true), но при этом у них (у всех или у некоторых) должна быть задана разная минимальная ширина? 

Я в таком случае прописываю у всех:  preferredWidth: 1000;  что бы желательная ширина у всех была одинаковая и обязательно больше, чем минимальная, иначе учитываться не будет (логично, да?)

Надеюсь, теперь больше с этим проблем не возникнет.

Кстати, такое же поведение будет если у компонента задано свойство implicitWidth или implicitHeight, потому что если мы не задали явно Layout.preferredWidth или  Layout.fillHeight, то берутся как раз они. Наверное вы спрашиваете: как быть, если это не просто прямоугольник, а какой-то компонент? То всё просто — оберните его в Item или Rectangle или задайте явно Layout.preferredWidth

Кстати, вот документация на лэйауты: тынц.

Вот как-то так =)

Views :

418