プロパティ バインディングにバグが発生し、挿入時にオブジェクトの位置が壊れる

StackOverflow https://stackoverflow.com//questions/21063748

質問

オブジェクトのツリーを作成します。ツリーは非常に単純なロジックを使用して描画されます。

  • 各オブジェクトは、それ自体の表現とその子 (およびその子など) の表現を加えたものと同じ大きさです。
  • オブジェクトがその親の最初の子の場合、その視覚的表現の下にインデントされて描画されます。
  • 次のすべてのオブジェクトは前の兄弟の直下に描画されます

ただし、この単純なアルゴリズムは、新しいオブジェクトを挿入するときに失敗する場合があります。例えば:

enter image description here

この場合、ノード 1 と 2 はノード 0 に正常に挿入され、ノード 0 の全高 (各ノードの左側の黒い線) は、その独自の UI とその両方の子の合計になります。

しかし、ノード 3 がノード 1 に挿入されると、レイアウトが崩れます。ノード 2 は押し下げられず、その結果、新しく挿入されたノードと重なり、黒い線が見えなくなります。

追加の更新サイクルがトリガーされると、ツリーは通常の状態に戻るため、不具合の原因は一時的な「or of sync」プロパティであると考えられます。追加の 1 回の更新で常にツリーが整理されるわけではないことに注意してください。挿入が行われた場所によっては、バグが配置の乱れから「カスケードアウト」するまでに数回の更新サイクルがかかる場合があります。

この異常な動作の原因や修正方法について何か考えはありますか?コードが時折壊れる原因は何でしょうか。また、最終的には動作しても、正常に動作するまでに 1 回から複数回の更新が必要になるのはなぜですか?

編集:そのレベルの最後のノードに終わらないノードに挿入するとき、アライメントが崩れるタイミングを特定して特定することができました。新しいノードが最後に「開いている」ノードに挿入されている限り、すべてのバインディングは適切に伝播します。そのレベルには次のノードはありませんが、前のノードに挿入するとバインディングが解除されます。したがって、影響を受けるすべてのレベルでバグを段階的に除去するには、追加の更新サイクルが必要です。しかし、それが起こったときだけ、何が原因でそれを修正するかはまだわかりません。

編集:もう少し詳しく調べてみると、新しい子が親項目に挿入され、その子が順序に基づいて再配置された後、 childrenRect 親のプロパティには、サイズの変更がすぐには反映されません。そのため、 に挿入されたオブジェクトの次の兄弟が配置されるとき、オブジェクトの高さには古い古い値が引き続き使用されますが、新しく挿入されたオブジェクトは使用しないため、影響を受けません。したがって、次の大きな問題は、バインディングが適切に行われ、アルゴリズムが適切なデータで期待どおりに動作できるように、どのように修正するかということだと思います。タイマーを使用して操作を遅らせようとしましたが、時間の問題ではなく、論理的な順序の問題のようです。バグを除去するために現在必要な更新の数は、バグが発生した部門に等しい。

編集:「解決」する方法は考えたのですが…実際にはそうではありません。原因と回避方法はまだわかりませんが、修正されるまでツリー全体を更新するよりももう少し「経済的な」解決策は、親をたどってルートに到達するまで現在のブランチのみを更新することです。 。これによりかなりの節約になりますが、少なくとも IMO ではバインディングが機能するはずなので、正しい値をすぐに取得することを好みます。

編集:注文コードは次のとおりです。

for (int i = 0; i < _children.size(); ++i) {
        UI * ui = _children.at(i)->_ui;
        if (ui) {
            if (i) {
                UI * prev = _children.at(i-1)->_ui;
                ui->setX(prev->x());
                ui->setY(prev->height() + prev->y());
            } else {
                ui->setX(Object::_helper->nodeSize());
                ui->setY(_ui->childOffset());
            }
        }
    }

QML の設定例:

UI {
    id: main
    width: childrenRect.width
    height: childrenRect.height    

    Item { 
        height: childrenRect.height

        Rectangle {
            width: Helper.nodeSize
            height: Helper.nodeSize
            color: "red"

            Text {
                anchors.centerIn: parent
                text: main.id
            }

            MouseArea {
                anchors.fill: parent   
                acceptedButtons: Qt.LeftButton | Qt.RightButton                 
                onClicked: {
                    if (mouse.button == Qt.RightButton)
                        Helper.gotoXY(main.absolutePosition())
                    else {
                        Helper.createObject(1, main)
                        Helper.updateRoot()
                    }
                }

                Rectangle {
                    height: main.height
                    width: 10
                    x: -10
                    color: "black"
                }
            }
        }
    }
}
役に立ちましたか?

解決

あなたがそれについて考えるとき、その行動は完全に理にかなっています、彼らが可能なもののあなたの期待と一緒に、バインディングに問題はありません。

あなたの「カスケードアップデート」の作品の仕事は非常にシンプルであり、あなたのコードが機能しない理由とその修復方法を指摘しています。

基本的には、新しいノードを挿入すると、すべての「ノード」を押します。これが、「最後の」ノードを挿入している限り、コードが破損しない理由です。しかし、それが最後にない場合、それはすべてを少し下に押しますが、この変更はその親が位置を更新するまで反映されません。 rootから更新を呼び出して挿入があるところが深くなると、最初のパスは高さのバインディングの最初の更新のみを引き起こします。そのため、次の更新サイクルその次の兄弟は適切に配置できます。挿入物が深かった場合は、起こる必要があるバインディングが多くなります。

新しいノードが正しく挿入されている理由は、それ自身の挿入が依然として更新されていない値を使用する次のアップデートまで変わらないプロパティに依存していないため、バインディングを解決するためにバインディングを評価することはできません。次のサイクル

これを行う最速の方法は、挿入レベルから始めることになり、「外側」および「下向き」の更新を伝播することになる。次のオブジェクトごとに、次の親の次の兄弟が、ルートオブジェクトを押すまで次の親の兄弟に更新します。これにより、挿入の影響を受けているオブジェクトの再配置は、現在の最善の解決策と比較してたくさんのサイクルを剃るはずです。

他のヒント

QML と JS を少しいじってみたところ、グラフィカル UI に問題がないことがわかりました。

私の デモプロジェクト (赤いボックスを左クリックまたは右クリックすることで) 子オブジェクトおよび兄弟オブジェクトをツリーに追加できます。ロジックは完全ではないかもしれませんが、基本は非常にスムーズに動作します。

main.qml

import QtQuick 2.1
import QtQuick.Controls 1.0

ApplicationWindow {
    id: app
    width: 800
    height: 600

    property int lastNodeId: 0
    function getId() { return lastNodeId++; }

    Level { }
}

Level.qml

import QtQuick 2.0

Item {
    id: frame
    width: childrenRect.width
    height: childrenRect.height

    Rectangle {
        width: 10
        color: "#000000"
        height: parent.height
        x: 0
        y: 0
    }

    Column {
        x: 10
        id: content

        Position { }
    }
}

兄弟を配置するための赤いボックスとその周囲のアイテム Position.qml

import QtQuick 2.0
import "component_creation.js" as CC

Item {
    id: position0
    width: childrenRect.width
    height: childrenRect.height

    Rectangle {
        color: "red"
        width: 80
        height: 30

        Text {
            anchors.fill: parent
            horizontalAlignment: Text.AlignHCenter
            verticalAlignment: Text.AlignVCenter

            Component.onCompleted: text = app.getId() // Set once, avoid bindung

            MouseArea {
                anchors.fill: parent
                acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
                onClicked: {
                    if (mouse.button == Qt.LeftButton)
                    {
                        CC.createChild(position0);
                    }
                    else if (mouse.button == Qt.RightButton)
                    {
                        CC.createSiblingAfter(position0)
                    }
                    else if (mouse.button == Qt.MiddleButton)
                    {
                        // no clue how to implement
                        // CC.createSiblingBefore(position0)
                    }
                }
            }
        }
    }
}

そしていくつかの JavaScript: component_creation.js

function createChild(parent_item) {
    var component = Qt.createComponent("Level.qml");
    if (component.status == Component.Ready)
    {
        var sprite = component.createObject(parent_item, {"x": 70, "y": 30});
        if (sprite == null) console.log("Error creating Level object");
    }
    else
    {
        console.log("Error loading component:", component.errorString());
    }
}

function createSiblingAfter(sibling_position)
{
    var component = Qt.createComponent("Position.qml");
    if (component.status == Component.Ready)
    {
        var sprite = component.createObject(sibling_position.parent);
        if (sprite == null) console.log("Error creating Position object");
    }
    else
    {
        console.log("Error loading component:", component.errorString());
    }
}

それが役立つことを願っています。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top