0

ファイルから uic によって生成された出力は、.ui常にレイアウトと子ウィジェットを既存のウィジェットにインストールします。これは、レイアウト/子を に直接追加する場合に最適ですwidget

#include "ui_form.h"

QWidget widget;
Ui::Form ui;
ui.setupUi(&widget);

残念ながら、介在するウィジェットが不要な場合もあります。まず、ウィジェットのレイアウトにはゼロ以外のマージンがあるためUi::Form、レイアウトにコンテンツを挿入する場合は、マージンをリセットする必要があります。

QWidget top;
QVBoxLayout layout{&top};
QWidget widget;
Ui::Form ui;
ui.setupUi(&widget);
widget.layout()->setContentsMargins(0,0,0,0);
layout.addWidget(&widget);

さらに、この場合、介在するウィジェットは完全に不要です。setupUiトップレベルのウィジェットを必要としなかったとします (Qt 5.7 以降、nullptr を渡すとクラッシュします)。次のように使用できます。

QWidget top;
QVBoxLayout layout{&top};
Ui::Form ui;
ui.setupUi(nullptr);
layout.addLayout(ui.layout); // assuming `layout` is the top-level layout there

調べuicたところ、トップレベルのウィジェットなしでコードを発行することはサポートされていません。必要なパッチがごくわずかであることは事実です。

それでも、Qt にパッチを適用せずにnullptr-acceptingの効果を実装する方法はありますsetupUiか?

誰かがこれをでっち上げの問題だと思わないように: この質問は、YUView のこのコードによって部分的に促されました。そこでは、parentWidget渡された にsetupUiすでにレイアウトがあり、その目的は、完了後にフォームのレイアウトを別のレイアウトに追加することsetupUiです。QWidget::setLayoutの内部から呼び出されてsetupUi、新しいレイアウトの設定を拒否し、警告を発し、それにもかかわらず、物事はたまたま機能します。このコードは、Qt コードが目的の結果につながる正しい方法でブレークすることに依存しているため、脆弱です。

4

1 に答える 1

1

はい。これを可能にする主な観察事項は次のとおりです。

  1. AchildLayoutは、その親を null にすることで親レイアウトから削除できます。

    childLayout->setParent(nullptr);
    
  2. 親のないレイアウトは、それをウィジェットまたは null 以外のレイアウトに追加すると、parentWidget()その子の親を変更します。

したがって、追加の最上位のラッパー レイアウトをフォームに追加し、ターゲットの最上位のレイアウトの親をそこから解除して、ラッパーを削除する必要があります。によって報告されたフォームの構造dumpObjectTreeは次のとおりです。

QWidget::Form 
    QVBoxLayout::wrapper 
        QVBoxLayout::layout 
            QHBoxLayout::horizontalLayout 
    QLabel::label 
    QLabel::label_2 
    QLabel::label_3

テストケース:

// https://github.com/KubaO/stackoverflown/tree/master/questions/layout-take-40497358
#include <QtWidgets>
#include "ui_form.h"

QLayout * takeLayout(QWidget *);

int main(int argc, char ** argv)
{
   QApplication app{argc, argv};

   QWidget parent;
   QHBoxLayout layout{&parent};
   Ui::Form ui;
   {
      QWidget w;
      ui.setupUi(&w);
      w.dumpObjectTree();
      if (true) {
         ui.layout->setParent(nullptr);
         delete ui.wrapper;
         layout.addLayout(ui.layout);
      } else {
         layout.addLayout(takeLayout(&w));
      }
   }
   parent.show();
   return app.exec();
}

この機能を除外しsetupLayoutたい場合は、代わりに使用するテンプレート関数が必要ですsetupUi。上記のテストケースは次のようになります。

   QWidget parent;
   QHBoxLayout layout{&parent};
   Ui::Form ui;
   layout.addLayout(setupLayout(&ui));
   parent.show();

setupLayout

//*** Interface

QLayout * setupLayout_impl(void * ui, void(*setupUi)(void * ui, QWidget * widget));

// Extracts the top layout from a Ui class generated by uic. The top layout must
// be enclosed in a wrapper layout.
template <typename Ui> QLayout * setupLayout(Ui * ui) {
  struct Helper {
    static void setupUi(void * ui, QWidget * widget) {
      reinterpret_cast<Ui*>(ui)->setupUi(widget);
    }
  };
  return setupLayout_impl(static_cast<void*>(ui), &Helper::setupUi);
}

//*** Implementation

static void unparentWidgets(QLayout * layout) {
  const int n = layout->count();
  for (int i = 0; i < n; ++i) {
    QLayoutItem * item = layout->itemAt(i);
    if (item->widget()) item->widget()->setParent(0);
    else if (item->layout()) unparentWidgets(item->layout());
  }
}

QLayout * setupLayout_impl(void * ui, void(*setupUi)(void * ui, QWidget * widget))
{
  QWidget widget;
  setupUi(ui, &widget);
  QLayout * wrapperLayout = widget.layout();
  Q_ASSERT(wrapperLayout);
  QObjectList const wrapperChildren = wrapperLayout->children();
  Q_ASSERT(wrapperChildren.size() == 1);
  QLayout * topLayout = qobject_cast<QLayout*>(wrapperChildren.first());
  Q_ASSERT(topLayout);
  topLayout->setParent(0);
  delete wrapperLayout;
  unparentWidgets(topLayout);
  Q_ASSERT(widget.findChildren<QObject*>().isEmpty());
  return topLayout;
}

フォームの構造を変更できない、または変更したくない場合はどうしますか? Qt の内部構造を使用takeLayoutして、ウィジェットのレイアウトを取り出し、それをウィジェットに関連付けずに返す関数を実装できます。privateQWidget::takeLayoutは、フラグが設定されたレイアウトを返すため、不十分であることに注意してくださいtopLevel。このようなレイアウトはウィジェットに直接インストールすることしかできず、(サブレイアウトとして) レイアウトに追加しようとするとアサーションに失敗します。方法は次のとおりです。

#include <private/qwidget_p.h>
#include <private/qlayout_p.h>

class WidgetHelper : private QWidget {
   struct LayoutHelper : private QLayout {
      static void resetTopLevel(QLayout * l) {
         auto d = static_cast<QLayoutPrivate*>(static_cast<LayoutHelper*>(l)->d_ptr.data());
         d->topLevel = false;
      }
   };
public:
   static QLayout * takeLayout(QWidget * w) {
      auto d = static_cast<QWidgetPrivate*>(static_cast<WidgetHelper*>(w)->d_ptr.data());
      auto l = w->layout();
      if (!l) return nullptr;
      d->layout = 0;
      l->setParent(nullptr);
      LayoutHelper::resetTopLevel(l);
      return l;
   }
};
QLayout * takeLayout(QWidget * w) { return WidgetHelper::takeLayout(w); }

次のform.uiように見えます。

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <layout class="QVBoxLayout" name="wrapper">
   <item>
    <layout class="QVBoxLayout" name="layout">
     <item>
      <widget class="QLabel" name="label">
       <property name="text">
        <string>Top</string>
       </property>
      </widget>
     </item>
     <item>
      <layout class="QHBoxLayout" name="horizontalLayout">
       <item>
        <widget class="QLabel" name="label_2">
         <property name="text">
          <string>Left</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLabel" name="label_3">
         <property name="text">
          <string>Right</string>
         </property>
        </widget>
       </item>
      </layout>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
</ui>
于 2016-11-08T22:14:35.450 に答える