Solved ListView as a radio button with radius on the border
-
Hello,
I'm trying to make some kind of radio button using a list view and the current item to define the choice.
The idea was to have an horizontal bar with this look :
Here the Option 2 is selected.It works as I would like except for one point... The Radius on the side.
I don't know how to clip the delegate and Highlight in the parent item.
Does anybody know how to achieve this ?Here is my component :
Rectangle { width: 200 height: 50 property var choix : [] //Set data here to have the list of options id: out color: "dimgrey" radius: height/2 ListView{ id : list anchors.fill: parent model: choix clip: true orientation: ListView.Horizontal delegate: radioDelegate highlight: highlightComponent highlightFollowsCurrentItem: false interactive: false onCurrentIndexChanged: { console.log("Changed : " + currentIndex) } } Component { id: highlightComponent Rectangle{ anchors.fill: list.currentItem color: "red" clip : true anchors.centerIn: list.currentItem y: list.currentItem.y x: list.currentItem.x } } Component{ id: radioDelegate Rectangle{ id: inner color: "transparent" height: parent.height width: out.width/choix.length border.color: "black" clip: true border.width: 1 Text { id: text text: choix[index] anchors.centerIn: parent font.pixelSize: 15 } MouseArea{ anchors.fill: parent onClicked: list.currentIndex = index } } } }
I call it with the following lines :
RadioList{ anchors.centerIn: parent width: 250 height: 50 choix: ["Option1","Option2","Option3"] }
Here is the result I do have when I select an item located on the side :
I want the left and right side to keep the radius when selected. -
Hi @DavidM29 , you can acheive that using Opacity mask.
Here is the sample code:-
Rectangle { id: masking height: buttonGridLayout.height width: buttonGridLayout.width radius: 50 visible: false } OpacityMask { height: buttonGridLayout.height width: buttonGridLayout.width source: buttonGridLayout maskSource: masking } RowLayout { id: buttonGridLayout anchors.fill: parent spacing: 0 opacity: 0 Repeater { model: 3 Rectangle { id: rootRect Layout.fillHeight: true Layout.fillWidth: true color: activeFocus ? "black" : "red" Text { text: index anchors.centerIn: parent color: "yellow" } MouseArea { anchors.fill: parent onClicked: { rootRect.forceActiveFocus() } } } } }
-
Hi @Shrinidhi-Upadhyaya
You should think that I'm annoying but unfortunatly I do have a limitation on the target device that makes any mask not working. I tried to use it to create a round gauge a few weeks ago and the device can't handle this kind masking. For some reasons it use a specific software that is bugged with my target device.Do you have any other ideas ?
-
@DavidM29 than I would suggest remove the highlight part all together, and change your delegate with this:
Rectangle{ id: inner color: list.currentIndex == index ? "red" :"transparent" .... }
I think index is an automatic assigned property.
-
Hi @DavidM29 , ohhh that's bad. Are there only 3 buttons or is it dynamic?
-
@J.Hilk
The issue is that the delegate is a Rectangle with no radius. The Radius in on the Rectangle that contains the ListView. If I do like you suggest the problem will be the same I believe.What I did is that :
I do have a Rectangle with radius like that :
In that rectangle I put the ListView made of transparent rectangle without radius.
Which means that if I put a color on that it will result in the same behaviour.@Shrinidhi-Upadhyaya
It is Dynamic it will be between 2 to 4 buttons I think. -
@DavidM29 oh in that case,
inside the "FrameRectangle" set theclip
property to true. That is more resource heavy but will do the trick -
@J.Hilk
It seems to be the same even with theclip:true
It set that property to that rectangle :
But I still have the same result.
Here is my complete component :
import QtQuick 2.0 Rectangle { width: 200 height: 50 property var choix : [] id: out color: "dimgrey" radius: height/2 clip: true ListView{ id : list anchors.fill: parent model: choix orientation: ListView.Horizontal delegate: radioDelegate highlight: highlightComponent highlightFollowsCurrentItem: false interactive: false } Component { id: highlightComponent Rectangle{ anchors.fill: list.currentItem color: "red" anchors.centerIn: list.currentItem y: list.currentItem.y x: list.currentItem.x } } Component{ id: radioDelegate Rectangle{ property bool selected : false id: inner color: "transparent" height: parent.height width: out.width/choix.length border.color: "black" border.width: 1 Text { id: text text: choix[index] anchors.centerIn: parent font.pixelSize: 15 } MouseArea{ anchors.fill: parent onClicked: list.currentIndex = index } } } }
-
Hi @DavidM29 ,
here is the sample code,try to run it and tell me whether it works fine or not and then we can go for optimization part.
RowLayout { id: buttonGridLayout anchors.fill: parent spacing: 5 Repeater { id: buttonRepeater model: 4 Item { id: rootRect Layout.fillHeight: true Layout.fillWidth: true Item { id: option1 anchors.fill: parent visible: index === 0 z: visible ? 1 : 0 MouseArea { anchors.fill: parent onClicked: { option1.forceActiveFocus() } } Rectangle { id: rect1 height: parent.height width: parent.width color: option1.activeFocus ? "red" : "black" radius: 50 } Rectangle { id: rect2 height: parent.height width: parent.width - 50 color: option1.activeFocus ? "red" : "black" anchors.left: parent.left anchors.leftMargin: 50 } } Rectangle { id: option2 anchors.fill: parent visible: (index === 0 || index === buttonRepeater.model - 1)?false:true color: option2.activeFocus?"red":"black" z: visible?1:0 MouseArea { anchors.fill: parent onClicked: { option2.forceActiveFocus() } } } Item { id: option3 anchors.fill: parent visible: index === buttonRepeater.model - 1 z: visible?1:0 MouseArea { anchors.fill: parent onClicked: { option3.forceActiveFocus() } } Rectangle { id: rect21 height: parent.height width: parent.width color: option3.activeFocus ? "red" : "black" radius: 50 anchors.right: parent.right } Rectangle { id: rect22 height: parent.height width: parent.width - 50 color: option3.activeFocus ? "red" : "black" } } Text { text: index anchors.centerIn: parent color: "yellow" z: 2 } } } }
-
@DavidM29
ok, time to dig in my bag of tricks ;)
I present RoundedRectangle.qmlimport QtQuick 2.11 Item { id:root property color color: "lightgrey" onColorChanged: canVas.requestPaint() property int radius: root.height/3 onRadiusChanged: canVas.requestPaint() property bool topLeftCorner: true property bool topRightCorner: false property bool bottomRightCorner: true property bool bottomLeftCorner: false onTopLeftCornerChanged: canVas.requestPaint() onTopRightCornerChanged: canVas.requestPaint() onBottomRightCornerChanged: canVas.requestPaint() onBottomLeftCornerChanged: canVas.requestPaint() property color borderColor: "black" onBorderColorChanged: canVas.requestPaint() property int borderWidth: 1 Canvas { id:canVas anchors.fill: parent onPaint: { var context = getContext("2d"); context.reset() context.beginPath(); //Start position context.moveTo(0,height / 2) //topLeftCorner if(topLeftCorner){ context.lineTo(0,radius) context.arcTo(0,0,radius, 0, radius); } else { context.lineTo(0,0) } //topRightCorner if(topRightCorner){ context.lineTo(width - radius, 0) context.arcTo(width, 0, width, radius, radius) } else { context.lineTo(width, 0) } //bottomRightCorner if(bottomRightCorner) { context.lineTo(width, height-radius) context.arcTo(width, height, width - radius, height, radius) } else { context.lineTo(width, height) } //bottomLeftCorner if(bottomLeftCorner) { context.lineTo(radius, height) context.arcTo(0, height, 0, height - radius, radius) } else { context.lineTo(0, height) } //Close path context.lineTo(height / 2) context.closePath() //Draw border context.lineWidth = borderWidth context.strokeStyle = borderColor context.stroke() //Draw background context.fillStyle = color context.fill(); } } }
to be used as:
import QtQuick 2.9 import QtQuick.Window 2.2 Window { visible: true width: 640 height: 200 title: qsTr("Hello World") Rectangle { id: frame anchors.centerIn: parent height: parent.height*2/3 width: parent.width *2/3 radius: height/2 color: "lightgrey" ListView{ id: lView anchors.fill: parent model: [1,2,3] orientation: ListView.Horizontal clip: true delegate: RoundedRectangle{ width: lView.width/lView.count height: lView.height color: lView.currentIndex == index ? "red" : "transparent" radius: frame.radius borderColor: "transparent" topLeftCorner: index == 0 topRightCorner: index == 2 bottomRightCorner: index ==2 bottomLeftCorner: index == 0 Text { anchors.centerIn: parent text: modelData } MouseArea{ anchors.fill: parent onClicked:{ lView.currentIndex = index } } } } } }
-
@J.Hilk
Oh that is perfect !
I was trying to make this kind of component whit 2 rectangle on top of each others.
I'm trying to implement that and mak sure it works on my target device. -
@J.Hilk
It works like a charm !
Thank you very much ! I never used any canvas I will have a look into it it seems to be a good tool.