Open QML ComboBox drop down menu from the current item position - qt

I have choose an item in combobox. Item's position is 300 for example. If I want to choose new element from combobox . Popup shows from beginning. I want to popup opened from current item position.
ComboBox {
id: control
model: ["First", "Second", "Third","MERHABA","NASILSIN","SELAM","IYIMISIN","DOSTUM","SUAN","BIR","DENEME YAPILIYOR"]
//width: 350
//font.pixelSize: 20
delegate: ItemDelegate {
width: 350
text: modelData
font.weight: control.currentIndex === index ? Font.DemiBold : Font.Normal
font.pixelSize: 30
highlighted: control.highlightedIndex == index
}

For QtQuick.Controls 2, There is a 'popup' property, so we can set the popup position with it's own property 'y', like this:
ComboBox {
model: ["First", "Second", "Third"]
delegate: ItemDelegate {
text: modelData
}
popup.y: y + height // popup just below ComboBox
}

The ComboBox will work the way you want it if the conditions allow for it, that is, if you have enough elements to fill the entire drop down list after the current index item it will show from that item rather than the beginning.
The stock ComboBox however doesn't seem to allow you to specify the drop down list height, so it will take significantly more elements than you have in your example. Or significantly taller elements.
Furthermore, if the current index is the last element, how do you imagine this will show? The list would show only the last element plus a whole bunch of nothing, which is not even possible, the last item cannot move up from the end of the list.
If you really want that behavior, you will have to implement your own combo box element from scratch.

I was facing the same issue and found out that if you place the popup onOpened, it works perfectly:
ComboBox {
id: yearDropdown
model: yearModel
onActivated: updateVisibleDate()
popup: Popup {
id: comboPopup
clip: true
contentItem: ListView {
id: listView
implicitHeight: contentHeight
model: yearDropdown.popup.visible ? yearDropdown.delegateModel : null
onModelChanged: if(model) positionViewAtIndex(yearDropdown.currentIndex, ListView.Center);
ScrollIndicator.vertical: ScrollIndicator { }
}
onOpened: {
x = yearDropdown.x //Set the position you want
y = yearDropdown.y + yearDropdown.implicitHeight //Set the position you want
}
}
}

Related

Qt QML Notify ListView when items in the model are added or removed

My ListView should always scroll its currently active item to the top between preferredHighlightBegin and preferredHighlightEnd. That works while clicking an item. However when a new item is added to my AbsctractListModel (And its not just added on the end but could be added anywhere in the middle), the ListView shows the new item, but it is not scrolled to the top. This is most likely because list.currentIndex = index is not set for new added items. I want to set it but did not find a way yet to do so. Does anyone have a hint?
I tried different signals such as onCurrentItemChanged but did not find the proper one yet which also gives me the correct index.
ListView {
id: list
anchors.fill: parent
clip: true
spacing: 11
focus: true
highlightMoveDuration: 400
snapMode: ListView.SnapToItem
preferredHighlightBegin: 0
preferredHighlightEnd: 100
highlightRangeMode: ListView.StrictlyEnforceRange
model: myAbstractListModel
delegate: ListItem {
height: 56
width: list.width
color: "red"
onListItemClicked: {
list.currentIndex = index
}
}
}
The expected result would be a ListView which scrolls a programmatically new added item of my AbstractListModel to the top (between preferredHighlightBegin and preferredHighlightEnd).
Adding this to your ListView will change the current index to the last one whenever the number of items in the model changes, you might want to keep track of the previous items count if you want to scroll only on insertion.
onCountChanged: {
if (count > 0) {
currentIndex = count - 1;
}
}
If the new items might be inserted anywhere in the model you could connect directly to it using the Connections item inside your ListView and connect it directly to the rowsInserted signal of QAbstractItemModel.
Connections {
target: model
onRowsInserted: {
currentIndex = first + 1;
}
}
If your model emits only the dataChanged signal you can try this.
Connections {
target: model
onDataChanged: {
currentIndex = topLeft.row() + 1;
}
}

QML ListView greys out or removes text of element when adjacent element is selected

So in testing my program, I discovered the strangest thing.
So, I have a ListView element with a custom C++ model, and a fairly simple delegate. Each delegate is a MyButton class, which is simply a Text class(z:2), an Image class(z:1), and a MouseArea class. That Image class is the background, and contains a translucent image which becomes opaque when MouseArea is onPressed().
Now the strange part.
When the ListView has 4 elements, it operates normally -except- when the user selects entry #3, then entry #2 or #1.
When the selected entry goes from #3->#1, the text in entry #2 grays out as opposed to its normal white.
When the selected entry goes from #3->#2, the text in entry #2 completely disappears.
After hours of testing and banging head against desk, I've uncovered a bit more:
The opacity of MyButton or any of its children never changes.
The color of MyButton's text element never changes
The content of MyButton's test element never changes
After offsetting the text partially outside of MyButton, this abnormal behavior only affects the text remaining inside the bounds of MyButton's Image child.
The Z level of MyButton or any of its children never changes, though it appears as if MyButton's Image is being placed on top of its Text.
Another image is never placed on top of a MyButton element. If this was the case, when going from #3->#1 you would see the image of entry #2 become darker.
When the ListView is scrolled, everything returns to normal.
When ListView contains 4 elements, below are the abnormalities:
when #4->#1: #2 and #3 gray out
when #4->#2: #2 disappears
when #4->#3: #3 disappears
when #3->#2: #2 disappears
when #3->#1: #2 grays out
This is consistent to the image and text inside the MyButton class being re-ordered, placing the image a Z level above the text. However the z levels are forced in the MyButton definition, and an onZChanged signal is never created when these events happen.
Below is the relevant code:
//MyButton:
import QtQuick 2.0
Item {
id: button
property string source: ""
property string source_toggled: source
property string button_text_alias: ""
signal pressed
width: button_image.sourceSize.width
height: button_image.sourceSize.height
property bool toggled: false
Image{
id: button_image
z: 1
source: toggled ? parent.source_toggled : parent.source
}
MyText{
z: 2
text_alias: button_text_alias
anchors.centerIn: parent
}
MouseArea {
id: button_mouse
anchors.fill: parent
onPressed: button.pressed()
}
}
//ListView:
Component{
id: p_button
MyButton{
source: picture_path + "bar.png"
source_toggled: picture_path + "bar_selected.png"
toggled: model.isCurrent
onClicked: {
profile_model.setCurrent(model.index)
}
button_text_alias: model.display
}
}
ListView{
id: p_list
width: 623
height: count*74 -1
spacing: 1
interactive: false
model: p_model
delegate: p_button
}
I can't think of -anything- that could cause this behavior.. any ideas?
I was able to solve this error by breaking up my delegate into:
Component{
id: p_button
Item{
property bool toggled: model.isCurrent
width: button_image.sourceSize.width
height: button_image.sourceSize.height
Image{
id: button_image
visible: !toggled
source: picture_path + "bar.png"
}
Image{
visible: toggled
source: picture_path + "bar_selected.png"
}
MouseArea{
anchors.fill: parent
onClicked: p_model.setCurrent(model.index)
}
MyText{
text_alias: model.display
anchors.centerIn: parent
}
}
}
So instead of swapping the source of an object, there are two objects which become visible/invisible based on a boolean value. This prevented the issue, though I still don't know the cause.

How to stop ListView for "jumping" when model is changed

What I need to do: I need to create a chat window using a ListView in QML that stores the chat-messages. I set listView.positionViewAtEnd() in order to follow the last messages. I disable positionViewAtEnd when I scroll upwards such that I can read the past messages without jumping at the end every time I receive a new message.
The problem: After scrolling up, every time I receive a new message it jumps at the beginning of list. To solve that I manage to store the contentY of the list and reset it every time onCountChanged handler is called (see the code below):
ListView {
id: messagesList
model: contact? contact.messages: []
delegate: delegate
anchors.fill: parent
anchors.bottomMargin: 20
height: parent.height
anchors.margins: 10
property int currentContentY
onMovementEnded: {
currentContentY = contentY
}
onCountChanged: {
contentY = currentContentY
}
onContentYChanged: {
console.log(".....contentY: " + contentY)
}
}
The problem is that even though I set the last contentY I had, before the model was changed, the list still jumps a bit (several pixels, not at the end or beginning) and it doesn't jump always. And when I go to the top of the list and print the contentY I get negative values. Theoretically, contentY at the beginning of the list should be 0.
Can somebody tell me what is going wrong? Or maybe suggest another solution to create my message list?
Than you in advance! :)
One possible solution schould be insert ListView into Flickable and disable interactive flag for ListView
Flickable {
id: fparent
anchors.fill: parent
anchors.bottomMargin: 20
anchors.margins: 10
interactive: true
clip: true
flickableDirection: Flickable.VerticalFlick
contentHeight: messagesList.height
ListView {
id: messagesList
width: parent.width
height: childrenRect.height
clip: true
model: contact? contact.messages: []
delegate: delegate
interactive: false
onCountChanged: {
fparents.returnToBounds();
}
}
}
Why not use onCountChanged slot in order to set the ListView at the end ?
onCountChanged: {
messagesList.positionViewAtEnd()
}

QML Gridview with InputText

I need a Listview with InputText on each row. Use case is a cart of items wherein qty can be changed for each row.
With the below code, Listview is rendering with a label and a text box alongside.
Issue is, all textboxes are going to have the same id, because of which, all textbox values are changing based on the last textbox value changed. Ex: If I enter 10 for the first item and 12 for the second item, even first item's qty changes to 12.
The onEnter is only for demonstration. It could be onFocusChanged going forward:
ListView {
id:list
model: fruitModel
x:10
y:10
width: parent.width-20
height: parent.height-20
clip:true
anchors.fill: parent
delegate: Row {
width: parent.width
height:30
Item
{
property variant myData: model
property alias value: text.text
Text {
id:text
width: parent.width
height: 30
text:name
}
InputText {
id: inp_box
text:qty
width:100
height:22
anchors.verticalCenterOffset: 15
anchors.horizontalCenterOffset:250
focus: true
onEnter: {
editQty(inp_box.text);
}
}
}
}
focus: true
snapMode: ListView.SnapOneItem
ScrollBar {
flickable: list
vertical: true
hideScrollBarsWhenStopped:false
}
}
You should extend your editQty function to take the index of edited entry in fruitModel as an additional parameter. The index of the current entry is available within the list view's delegate as index. The way you did it, editQty has no chance to know which entry to change.

How to define a "template" with child placeholders in QML?

I really like QML. I like how I can define components (comparable to classes) and their properties, and instantiate them from somewhere else (comparable to objects).
I can define, let's say, a button, having some look and feel, and a label text on it. This could be done, for example, using this component definition (Button.qml):
Item {
id: button
property string label
anchors.fill: parent
Rectangle {
anchors.fill: parent
radius: 10
color: "gray"
Text {
anchors.centerIn: parent
font.pixelSize: 20
text: button.label
color: "white"
}
}
}
and instanciated in this main file (main.qml):
Rectangle {
width: 300
height: 200
Button {
anchors.centerIn: parent
anchors.margins: 50
label: "Hello button!"
}
}
But I see the following restriction: I can only define a button template with some properties, not with some placeholder. All children defined in the instance will be direct children, at least per default, and I want to change this behavior.
Let's say I want to place an item (let's say an image, but I don't want to tell the definition of Button that it will be an image) in the button. I imagine something like this:
Item {
id: button
property Item contents <-- the client can set the placeholder content here
anchors.fill: parent
Rectangle {
anchors.fill: parent
radius: 10
color: "gray"
Item {
id: placeholder <-- where the placeholder should be inserted
}
}
Component.onCompleted: {
// move the contents into the placeholder...
}
}
How can I achieve this? I don't know if using Component.onCompleted is the correct way. Note that, however, that in my case the contents will never change afterwards (at least in my current design of the application...).
Also, I want anchoring to work within the placeholder. For example, if I define the contents to be a Text element, being centered in its parent (which will first be the template itself). Then my code moves this Text instance into the placeholder and the parent anchors should then be those of the placeholder item, not the template item.
I found a much nicer answer to this question, suggested in a presentation of the Qt Developer Days 2011 "Qt Quick Best Practices and Design Patterns".
They use default property alias ... to alias the child items to any property of any item. If you don't want to alias the children but give the alias property a name, just remove default. (Literal children are per QML definition the value of the default property.)
Item {
id: button
default property alias contents: placeholder.children
anchors.fill: parent
Rectangle {
anchors.fill: parent
radius: 10
color: "gray"
Item {
id: placeholder <-- where the placeholder should be inserted
}
}
}
Necro answering in case someone else end up here as I did.
In Qt5 somewhere along the line the default property became "data" and not "children".
This makes it possible to add other object types than "Item".
e.g. Connections can be added as well (to answer my own question above)
So in Qt5 you should do:
Item {
id: button
default property alias contents: placeholder.data
anchors.fill: parent
Rectangle {
anchors.fill: parent
radius: 10
color: "gray"
Item {
id: placeholder <-- where the placeholder should be inserted
}
}
}
Note the:
placeholder.data instead of placeholder.children
Also please note that you don't have to use the alias name contents - this can be anything you like. An example:
Item {
id: button
default property alias foo: placeholder.data
...
}
Actually, the correct answer from what I've heard is to use a QML Loader to accomplish what you want.
[that being said; I haven't actually tried it yet but it's on my near-term to-try list and looks fairly straight forward]
Also, search stackoverflow for other "QML Loader" questions as there are a number that will help you get started.
You can move the item(s) (if you want to support multiple items within the placeholder) using this piece of code:
property list<Item> contents
Component.onCompleted: {
var contentItems = [];
for(var i = 0; i < contents.length; ++i)
contentItems.push(contents[i]);
placeholder.children = contentItems;
}
Note that you do not have to provide a list of Items for the contents property, as single values will be accepted by the QML engine also for list properties.
In short (to show the idea):
import QtQuick 1.1
Item {
width: 200
height: 100
//<-- the client can set the placeholder content here
property Item contents: Rectangle {
anchors.fill: parent
anchors.margins: 25
color: "red"
}
Rectangle {
id: container
anchors.fill: parent
radius: 10
color: "gray"
//<-- where the placeholder should be inserted
}
Component.onCompleted: {
contents.parent = container
}
}
Somewhat longer version (supporting contents reassignment):
import QtQuick 1.1
Item {
width: 200
height: 100
//<-- the client can set the placeholder content here
property Item contents: Rectangle {
//anchors can be "presupplied", or set within the insertion code
//anchors.fill: parent
//anchors.margins: 25
color: "red"
}
Rectangle {
id: container
anchors.fill: parent
radius: 10
color: "gray"
//<-- where the placeholder should be inserted
//Item {
// id: placeholder
//}
}
//"__" means private by QML convention
function __insertContents() {
// move the contents into the placeholder...
contents.parent = container
contents.anchors.fill = container
contents.anchors.margins = 25
}
onContentsChanged: {
if (contents !== null)
__insertContents()
}
Component.onCompleted: {
__insertContents()
}
}
Hope this helps :)

Resources