私の難問は、一般的には次のとおりです。GridViewの外部のアクションを通じて、以前に選択された特定のモデルアイテムまたはインデックスのみに基づいて、GridView内の特定のデリゲートアイテムの座標を把握したいと思います。
モデルに多数のアイテムがあるGridViewがあります。 GridViewのデリゲートは、各アイテムのサムネイルビューを作成します。クリックすると、アイテムの詳細なフルスクリーンビューが表示されます。サムネイルがGridViewのその場所から拡大して表示され、詳細ビューが閉じられると、縮小して元のGridViewに戻る、素敵な移行が必要です。
コツは、詳細ビュー自体がListViewのデリゲートであるため、一度に1つの画面で詳細ビュー間をページングできることです。これは、GridViewのデリゲート項目のサイズを変更するだけのソリューションでは、機能しないことを意味します。また、ListView内の任意のアイテムにページングできるため、GridViewに戻るには、モデルまたはモデルアイテムのインデックスで使用可能な情報のみに基づいて行う必要があります(たとえば、詳細ビューか何か)。
デリゲートアイテムには、独自の配置を知っているクリックハンドラーのMouseAreaがあり、アニメーションを開始する関数に渡すことができるので、拡張アニメーションはかなり簡単です。それは私が理解できない逆です:ListViewのモデルアイテム/インデックスから、GridViewの関連アイテムの座標をどのように把握するのですか?
モデルアイテムインスタンスまたはインデックスからデリゲートアイテムインスタンスにアクセスできるように見えるドキュメントには何も見つかりません。 GridViewには、座標に基づいてインデックスを返すindexAt()
があります。逆はうまくいくと思いますが、実在しないようです。
これはより具体的な例です。長さの謝罪;これは、私の問題を正確に説明する、思いつくことができる最も短いコード例です。
import QtQuick 1.1
Item {
id: window
width: 400
height: 200
state: "summary"
states: [
State { name: "summary"; },
State { name: "details"; }
]
transitions: [
Transition { from: "summary"; to: "details";
SequentialAnimation {
PropertyAction { target: animationRect; property: "visible"; value: true; }
ParallelAnimation {
NumberAnimation { target: animationRect; properties: "x,y"; to: 0; duration: 200; }
NumberAnimation { target: animationRect; property: "width"; to: 400; duration: 200; }
NumberAnimation { target: animationRect; property: "height"; to: 200; duration: 200; }
}
PropertyAction { target: detailsView; property: "visible"; value: true; }
PropertyAction { target: summaryView; property: "visible"; value: false; }
PropertyAction { target: animationRect; property: "visible"; value: false; }
}
},
Transition { from: "details"; to: "summary";
SequentialAnimation {
PropertyAction { target: summaryView; property: "visible"; value: true; }
// How to animate animationRect back down to the correct item?
PropertyAction { target: detailsView; property: "visible"; value: false; }
}
}
]
Rectangle {
id: animationRect
z: 1
color: "gray"
visible: false
function positionOverSummary(summaryRect) {
x = summaryRect.x; y = summaryRect.y;
width = summaryRect.width; height = summaryRect.height;
}
}
ListModel {
id: data
ListElement { summary: "Item 1"; description: "Lorem ipsum..."; }
ListElement { summary: "Item 2"; description: "Blah blah..."; }
ListElement { summary: "Item 3"; description: "Hurf burf..."; }
}
GridView {
id: summaryView
anchors.fill: parent
cellWidth: 100
cellHeight: 100
model: data
delegate: Rectangle {
color: "lightgray"
width: 95; height: 95;
Text { text: summary; }
MouseArea {
anchors.fill: parent
onClicked: {
var delegateRect = mapToItem(window, x, y);
delegateRect.width = width; delegateRect.height = height;
animationRect.positionOverSummary(delegateRect);
detailsView.positionViewAtIndex(index, ListView.Beginning);
window.state = "details";
}
}
}
}
ListView {
id: detailsView
anchors.fill: parent
visible: false
orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem
model: data
delegate: Rectangle {
color: "gray"
width: 400; height: 200;
Column {
Text { text: summary; }
Text { text: description; }
}
MouseArea {
anchors.fill: parent
onClicked: {
// How do I get the coordinates to where animationRect should return?
summaryView.positionViewAtIndex(index, GridView.Visible);
window.state = "summary";
}
}
}
}
}
何か案は?これを間違った方法で行っている可能性があります。私が特にやろうとしていることが不可能である場合、これを構築する方法は他にありますか?ありがとう!
編集:私が持っているいくつかのアイデア(どれも実現可能ではないと私は信じています):
Component.onCompleted
およびComponent.onDestruction
を使用して、作成されたすべてのデリゲートアイテムのリストを保持します。役立つためには、モデルアイテムまたはインデックス=>デリゲートアイテムのマップである必要があります。問題は、 基本タイプのドキュメント (特に variant )が、この種のマップを純粋なQMLで作成することが不可能であることを示しているようです。したがって、これはこのマップをC++クラスとして作成し、それをQMLでonCompleted
/onDestruction
とともにデリゲートコンポーネント内で使用して最新の状態に保つことを意味すると思われます。シンプルである必要があるため、少し危険で強引なようです。
このメーリングリストの投稿 は、FlickableのcontentItem
プロパティを使用して、デリゲートアイテムを列挙できることを示しているようです。それから私は この投稿 をそれを悪い習慣として呼び出すことを見つけました。まだ調査中ですが、これが正当な解決策になるかどうかは疑問です。信頼性の高い方法で動作するにはあまりにもハッキリすぎるようです。
私がこれまでに得たのはそれだけです。
調査の結果、contentItem
はFlickableのインスタンス化されたデリゲートを保持していることがわかりました。上で述べたように、これが本当にこれを行うための最良の方法、または良い方法の期間でさえあるかどうか私は懐疑的ですが、それはうまくいくようです。このハッキーなソリューションの完全なコードを以下に掲載しますが、もっと良い方法があることを願っています。本当に重要なのは、GridViewの新しいgetDelegateInstanceAt()
関数です。
import QtQuick 1.1
Item {
id: window
width: 400
height: 200
state: "summary"
states: [
State { name: "summary"; },
State { name: "details"; }
]
transitions: [
Transition { from: "summary"; to: "details";
SequentialAnimation {
PropertyAction { target: animationRect; property: "visible"; value: true; }
ParallelAnimation {
NumberAnimation { target: animationRect; properties: "x,y"; to: 0; duration: 200; }
NumberAnimation { target: animationRect; property: "width"; to: 400; duration: 200; }
NumberAnimation { target: animationRect; property: "height"; to: 200; duration: 200; }
}
PropertyAction { target: detailsView; property: "visible"; value: true; }
PropertyAction { target: summaryView; property: "visible"; value: false; }
PropertyAction { target: animationRect; property: "visible"; value: false; }
}
},
Transition { from: "details"; to: "summary";
id: shrinkTransition
property variant destRect: {"x": 0, "y": 0, "width": 0, "height": 0}
SequentialAnimation {
PropertyAction { target: summaryView; property: "visible"; value: true; }
PropertyAction { target: animationRect; property: "visible"; value: true; }
PropertyAction { target: detailsView; property: "visible"; value: false; }
ParallelAnimation {
NumberAnimation { target: animationRect; property: "x"; to: shrinkTransition.destRect.x; duration: 200; }
NumberAnimation { target: animationRect; property: "y"; to: shrinkTransition.destRect.y; duration: 200; }
NumberAnimation { target: animationRect; property: "width"; to: shrinkTransition.destRect.width; duration: 200; }
NumberAnimation { target: animationRect; property: "height"; to: shrinkTransition.destRect.height; duration: 200; }
}
PropertyAction { target: animationRect; property: "visible"; value: false; }
}
}
]
Rectangle {
id: animationRect
z: 1
color: "gray"
visible: false
function positionOverSummary(summaryRect) {
x = summaryRect.x; y = summaryRect.y;
width = summaryRect.width; height = summaryRect.height;
}
function prepareForShrinkingTo(summaryRect) {
x = 0; y = 0;
width = 400; height = 200;
shrinkTransition.destRect = summaryRect;
}
}
ListModel {
id: data
ListElement { summary: "Item 1"; description: "Lorem ipsum..."; }
ListElement { summary: "Item 2"; description: "Blah blah..."; }
ListElement { summary: "Item 3"; description: "Hurf burf..."; }
}
GridView {
id: summaryView
anchors.fill: parent
cellWidth: 100
cellHeight: 100
model: data
delegate: Rectangle {
// These are needed for getDelegateInstanceAt() below.
objectName: "summaryDelegate"
property int index: model.index
color: "lightgray"
width: 95; height: 95;
Text { text: summary; }
MouseArea {
anchors.fill: parent
onClicked: {
var delegateRect = mapToItem(window, x, y);
delegateRect.width = width; delegateRect.height = height;
animationRect.positionOverSummary(delegateRect);
detailsView.positionViewAtIndex(index, ListView.Beginning);
window.state = "details";
}
}
}
// Uses black magic to hunt for the delegate instance with the given
// index. Returns undefined if there's no currently instantiated
// delegate with that index.
function getDelegateInstanceAt(index) {
for(var i = 0; i < contentItem.children.length; ++i) {
var item = contentItem.children[i];
// We have to check for the specific objectName we gave our
// delegates above, since we also get some items that are not
// our delegates here.
if (item.objectName == "summaryDelegate" && item.index == index)
return item;
}
return undefined;
}
}
ListView {
id: detailsView
anchors.fill: parent
visible: false
orientation: ListView.Horizontal
snapMode: ListView.SnapOneItem
model: data
delegate: Rectangle {
color: "gray"
width: 400; height: 200;
Column {
Text { text: summary; }
Text { text: description; }
}
MouseArea {
anchors.fill: parent
onClicked: {
summaryView.positionViewAtIndex(index, GridView.Visible);
var delegateInstance = summaryView.getDelegateInstanceAt(index);
var delegateRect = window.mapFromItem(summaryView,
delegateInstance.x - summaryView.contentX,
delegateInstance.y - summaryView.contentY
);
delegateRect.width = delegateInstance.width;
delegateRect.height = delegateInstance.height;
animationRect.prepareForShrinkingTo(delegateRect);
window.state = "summary";
}
}
}
}
}
より堅牢な方法があることを教えてください!
C++でこれを実現する方法に興味がある人のために、簡単なスニペットを次に示します。
_//get the reference to GridView somehow (QObject *obj) (omitted for brevity)
QQuickItem *x = qobject_cast<QQuickItem *>(obj);
if (x->property("contentItem").isValid()) {
QQuickItem *o = qvariant_cast<QQuickItem *>(x->property("contentItem"));
qDebug() << "Extracting content item " << o->metaObject()->className() << " from " << x->metaObject()->className();
qDebug() << "item has ch count " << o->childItems().count();
}
_
QQuickItem
からchildren()
にアクセスするとQObject::children()
メソッドが呼び出され、必ずしも同じオブジェクトを返さないことに注意してください(これを投稿する主な理由) QQuickItem::childItems()
として。多分誰かがこれが便利だと思うかもしれません、それは確かに4日前に私を助けるでしょう... :)
QMLタイプのListViewの場合、次の単純な関数で、特定のインデックスにあるデリゲートインスタンスを取得できます。
function getDelegateInstanceAt(index) {
return contentItem.children[index];
}
以下は、Qt 5.5に基づいて、エラーチェックとロギングコードを追加した、上記の関数を使用するQMLテストの例です。
import QtQuick 2.0
import QtQuick.Controls 1.2
Rectangle {
width: 400
height: 200
ListView { id: fruitView
width: parent.width
height: parent.height / 2
anchors.left: parent.left
anchors.top: parent.top
model: fruitModel
delegate: TextInput {
text: fruit_name
}
// Function to get the delegate instance at a specific index:
// =========================================================
function getDelegateInstanceAt(index) {
console.log("D/getDelegateInstanceAt[" + index + "]");
var len = contentItem.children.length;
console.log("V/getDelegateInstanceAt: len[" + len + "]");
if(len > 0 && index > -1 && index < len) {
return contentItem.children[index];
} else {
console.log("E/getDelegateInstanceAt: index[" + index + "] is invalid w.r.t len[" + len + "]");
return undefined;
}
}
}
Rectangle {
width: parent.width
height: parent.height / 2
anchors.left: parent.left
anchors.bottom: parent.bottom
Button {
anchors.centerIn: parent
text: "getDelegateInstanceAt(1)"
onClicked: {
// Code to test function getDelegateInstanceAt():
var index = 1;
var myDelegateItem1 = fruitView.getDelegateInstanceAt(index);
if(myDelegateItem1) {
console.log("I/onClicked: found item at index[" + index + "] fruit_name[" + myDelegateItem1.text + "]"); // Should see: fruit_name[Banana_1]
} else {
console.log("E/onClicked: item at index[" + index + "] is not found.");
}
}
}
}
ListModel { id: fruitModel
ListElement { fruit_name: "Apple_0" }
ListElement { fruit_name: "Banana_1" }
ListElement { fruit_name: "Cherry_2" }
}
}
上記の関数は、これらの単純な「fruitModel」および「fruitView」オブジェクトでうまく機能しますが、より複雑なListModelおよびListViewインスタンスを処理する場合は、さらに拡張する必要がある場合があります。
QMLプログラミングのほんの数か月後に、OPの1つに似たこのソリューションを思い付きましたが、他の人がこのことを整理するのに役立つことを期待して、とにかくそれを投稿します。
この例には基本的に3つのコンポーネントがあります。ビジュアルコンポーネント(ビューア)を表示するGridView
、Item
(データ)のリストを含むQObjects
、およびComponent
は、色付きのRectangle
(デリゲート)を囲みます。
ビューアはデータを受け取り、デリゲートを作成することを示します。このパターンに従って、デリゲートのコンテンツを変更する最も簡単な方法は、そのデータにアクセスすることです。デリゲートのデータにアクセスするには、最初にlistmodel
オブジェクトに到達する必要があります。この例では、_grid.children[1]
_を介してアクセスできます(理由は_grid.children[0]
_にない理由はわかりませんが、これはは単なる詳細です)、最後に_grid.children[1].list_model[index]
_を介して適切なデリゲートにアクセスします。これは、getChild()
関数内に簡単にパッケージ化できます。
最後の推奨事項は、開発の開始以来、意味のあるobjectName
をほぼすべてに与えることです。これにより、デバッグが大幅に容易になり、たとえそれが悪であっても、_C++
_からのデータへのアクセスが可能になります。
_GridView
{
id: grid
objectName: "grid"
cellWidth: 50
cellHeight: 50
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: 100
width: 100
height: 100
model: listmodel.list_model
delegate: delegate
focus: false
interactive: false
function getChild(index)
{
var listmodel = grid.children[1].list_model
var elem = listmodel[index]
return elem
}
Component.onCompleted:
{
for (var idx = 0; idx < grid.children.length; idx++)
{
console.log("grid.children[" + idx + "].objectName: " + grid.children[idx].objectName)
}
var elem = getChild(2)
elem.model_text += " mod"
elem.model_color = "slateblue"
}
Item
{
id: listmodel
objectName: "listmodel"
// http://www.w3.org/TR/SVG/types.html#ColorKeywords
property list<QtObject> list_model:
[
QtObject
{
objectName: "rectmodel" + model_idx
property int model_idx: 1
property string model_text: "R" + model_idx
property color model_color: "crimson"
property bool model_visible: true
},
QtObject
{
objectName: "rectmodel" + model_idx
property int model_idx: 2
property string model_text: "R" + model_idx
property color model_color: "lawngreen"
property bool model_visible: true
},
QtObject
{
objectName: "rectmodel" + model_idx
property int model_idx: 3
property string model_text: "R" + model_idx
property color model_color: "steelblue"
property bool model_visible: true
},
QtObject
{
objectName: "rectmodel" + model_idx
property int model_idx: 4
property string model_text: "R" + model_idx
property color model_color: "gold"
property bool model_visible: true
}
]
}
Component
{
id: delegate
Rectangle
{
id: delegaterect
objectName: "delegaterect"
width: grid.cellWidth
height: grid.cellHeight
color: model_color
visible: model_visible
Component.onCompleted:
{
console.log("delegaterect.children[0].objectName: " + delegaterect.children[0].objectName + " - " + delegaterect.children[0].text)
}
Text
{
id: delegatetext
objectName: "delegatetext"
anchors.centerIn: parent
text: qsTr(model_text)
}
}
}
}
_