2012-01-27 19 views
16

Câu hỏi hóc búa của tôi nói chung là: thông qua một số hành động bên ngoài GridView, tôi muốn tìm ra tọa độ của một mục đại biểu cụ thể trong GridView chỉ dựa trên một mục mẫu hoặc chỉ mục cụ thể được chọn trước đó.Làm thế nào để có được một thành phần ủy nhiệm tức thời từ một GridView hoặc ListView trong QML

Tôi đã có GridView với một số mục trong mô hình. Các đại biểu của GridView tạo ra một cái nhìn hình thu nhỏ của mỗi mục. Khi được nhấp, nó sẽ hiển thị chế độ xem chi tiết, toàn màn hình của mục. Tôi muốn có một quá trình chuyển đổi tốt đẹp cho thấy hình thu nhỏ mở rộng ra khỏi vị trí của nó trong GridView và khi chế độ xem chi tiết bị loại bỏ, hãy thu hẹp lại vào GridView tại chỗ.

Bí quyết là, chế độ xem chi tiết tự nó là đại biểu của một ListView để bạn có thể trang giữa các lần xem chi tiết một màn hình tại một thời điểm. Điều này có nghĩa là một giải pháp đơn giản là thay đổi kích thước mục đại biểu của GridView hoặc một cái gì đó sẽ không hoạt động. Ngoài ra, vì bạn có thể trang cho bất kỳ mục nào trong ListView, nên việc quay trở lại GridView phải được thực hiện chỉ dựa trên thông tin có sẵn trong mô hình hoặc chỉ mục của mục mẫu (ví dụ: tôi không thể lưu trữ các liên kết của MouseArea được sử dụng để khởi chạy xem chi tiết hoặc một cái gì đó).

Hoạt ảnh mở rộng khá dễ dàng, vì mục đại biểu có MouseArea cho trình xử lý nhấp chuột biết vị trí riêng của nó, để có thể truyền cho hàm bắt đầu hoạt ảnh. Đó là ngược lại tôi không thể tìm ra: từ mô hình mục/chỉ mục trong ListView, làm thế nào để tôi tìm ra các tọa độ của các mục liên quan trong GridView?

Tôi không thể tìm thấy bất kỳ nội dung nào trong tài liệu dường như cho phép bạn có quyền truy cập vào một cá thể đại biểu từ một cá thể mẫu hoặc thậm chí là chỉ mục. GridView có indexAt() trả về chỉ mục dựa trên tọa độ. Tôi nghĩ tôi có thể làm được điều ngược lại, nhưng nó dường như không tồn tại.

Dưới đây là ví dụ cụ thể hơn. Xin lỗi cho chiều dài; đây là mã ví dụ ngắn nhất tôi có thể nghĩ ra chính xác mô tả sự cố của tôi:

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"; 
       } 
      } 
     } 
    } 
} 

Bất kỳ ý tưởng nào? Có thể tôi đang đi về điều này một cách sai lầm. Nếu những gì tôi đang cố gắng làm đặc biệt là không thể, có cách nào khác tôi nên kiến ​​trúc này? Cảm ơn!


Edit: Một số ý tưởng tôi đã có (không ai trong số đó tôi tin là khả thi):

  • Giữ một danh sách tất cả các mặt hàng đại biểu được tạo ra, sử dụng Component.onCompletedComponent.onDestruction. Để hữu ích, nó phải là một bản đồ của mục mẫu hoặc chỉ mục => mục đại biểu. Rắc rối là, basic types docs (đặc biệt là variant) dường như chỉ ra rằng loại bản đồ này không thể tạo trong QML thuần túy. Vì vậy, có vẻ như điều này có nghĩa là tạo bản đồ này dưới dạng lớp C++ và sử dụng bản đồ đó trong QML với onCompleted/onDestruction trong thành phần đại biểu để luôn cập nhật. Có vẻ hơi nguy hiểm và nặng tay cho một thứ đơn giản.

  • This mailing list post dường như cho biết thuộc tính contentItem của Flickable có thể được sử dụng để liệt kê các mục đại biểu. Sau đó, tôi tìm thấy this post gọi đó là thực tiễn không tốt. Tôi vẫn đang xem xét nó, nhưng tôi hoài nghi đây sẽ là một giải pháp hợp pháp. Có vẻ như quá khó để làm việc đáng tin cậy.

Đó là tất cả những gì tôi có cho đến nay.

Trả lời

9

Sau một số điều tra, nó chỉ ra rằng contentItem không giữ các đại biểu được khởi tạo cho một Flickable. Như tôi đã nói ở trên, tôi nghi ngờ rằng đây thực sự là cách tốt nhất để làm điều này, hoặc thậm chí là một khoảng thời gian tốt, nhưng nó dường như làm việc. Tôi sẽ đăng mã đầy đủ cho giải pháp hacky dưới đây, nhưng tôi vẫn hy vọng có một cách tốt hơn. Bit thực sự quan trọng là hàm getDelegateInstanceAt() mới trong GridView.

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"; 
       } 
      } 
     } 
    } 
} 

Hãy cho tôi biết có cách mạnh mẽ hơn!

+0

Chỉ cần tự hỏi nếu bạn đã gặp phải bất kỳ giải pháp tốt hơn hoặc triển khai tốt hơn để truy cập các đại biểu của một mô hình từ một tệp QML khác hoặc nếu bạn vẫn đang sử dụng phương pháp trên? – stackunderflow

+0

Không, theo như tôi có thể nói đây là cách "chính thức" để làm điều đó. – chazomaticus

+0

Có vẻ như một trong những vấn đề mà "ma thuật đen" cố gắng khắc phục là danh sách trẻ em bị ô nhiễm bởi anh chị em của GridView. Tôi nghĩ rằng bạn có thể bọc GridView của bạn trong một hình chữ nhật, đơn giản hóa danh sách con của bạn. Sau đó, chỉ đơn giản là summaryView.contentItem.children [i] dường như hoạt động tốt. – SketchBookGames

2

Chỉ trong trường hợp có ai quan tâm vào làm thế nào để thực hiện điều này trong C++, đây là một đoạn nhanh:

//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(); 
} 

Điều quan trọng cần lưu ý (và lý do chính đã gửi bài này), mà việc tiếp cận children() từ QQuickItem sẽ gọi phương thức QObject::children(), không nhất thiết phải trả về cùng một đối tượng như QQuickItem::childItems(). Có thể ai đó thấy điều này hữu ích, nó chắc chắn sẽ giúp tôi cách đây bốn ngày ... :)

0

Chỉ sau vài tháng lập trình QML tôi đã đưa ra giải pháp này, tương tự như giải pháp OP, nhưng Tôi sẽ đăng nó với hy vọng rằng nó có thể giúp những người khác phân loại thứ này.

Về cơ bản có ba thành phần trong ví dụ này: a GridView để hiển thị các thành phần thị giác (xem), một Item có chứa một danh sách các QObjects (dữ liệu) và một Component đó kèm theo một màu Rectangle (đại biểu).

Trình xem lấy dữ liệu và cho thấy nó tạo đại biểu. Theo mẫu này, cách dễ nhất để thay đổi nội dung của một đại biểu là truy cập dữ liệu của nó. Để truy cập dữ liệu của một đại biểu, trước hết phải truy cập vào đối tượng listmodel, trong ví dụ này, có thể truy cập thông qua grid.children[1] (không chắc chắn lý do tại sao không ở số grid.children[0], nhưng đây chỉ là một chi tiết) và cuối cùng truy cập vào đại biểu phù hợp grid.children[1].list_model[index]. Điều này có thể được đóng gói thuận tiện bên trong một hàm getChild().

Đề xuất cuối cùng là đưa ra ý nghĩa objectName tới gần như mọi thứ kể từ khi bắt đầu phát triển. Thực hành này dễ dàng gỡ lỗi rất nhiều và, ngay cả khi nó là ác, cho phép truy cập dữ liệu từ 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) 
      } 
     } 
    } 
} 
0

Đối QML loại ListView, chức năng đơn giản sau đây có thể được thể hiện ủy nhiệm tại một chỉ số cụ thể:

function getDelegateInstanceAt(index) { 
    return contentItem.children[index]; 
} 

Sau đây là một ví dụ kiểm tra QML mà làm cho sử dụng các chức năng trên, với lỗi thêm mã kiểm tra và ghi nhật ký, dựa trên Qt 5.5:

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" } 
    } 
} 

Mặc dù chức năng trên hoạt động tốt với các đối tượng "fruitModel" và "fruitView" đơn giản này, có thể cần phải tiếp tục nhai khi giao dịch với các phiên bản ListModel và ListView phức tạp hơn.

Các vấn đề liên quan