Search code examples
qtqmlqtquickcontrols2

Textfield with an icon and placeholder text


is there a way to get a textfield to display the placeholder text next to an icon like in the picture below.

I tried doing it by setting the padding, but that only works for the actual text not the placeholder text.

import QtQuick
import QtQuick.Controls

TextField {
    property alias iconSource: image.source

    selectByMouse: true
    leftPadding: iconSource.toString() ? 32 : 16

    Image {
        id: image
        anchors.verticalCenter: parent.verticalCenter
        width: parent.height * 0.5
        height: width
        sourceSize.width: width
        sourceSize.height: height
    }
}

It looks ok if the textfield is focused. If it is out of focus, the text is overlapped by the icon (see password field):

enter image description here

But I want it to look like this:

enter image description here


Solution

  • The TextField in Material style uses Material.textFieldHorizontalPadding (source code). And since textFieldHorizontalPadding is read-only and there is no alias defined for placeholder, you cannot change the placeholder's x position directly.

    There are two options here:

    • You can either copy and paste the source code from material/TextField.qml and modify it, as shown below:

      T.TextField {
          // ...
      +   property real holderPadding: control.Material.textFieldHorizontalPadding
          // ...
          FloatingPlaceholderText {
              // ...
      +       x: control.holderPadding
      -       x: control.Material.textFieldHorizontalPadding
              // ...
          }
      }
      
    • Or, you can access the placeholder using the children property (or visibleChildren).

      The current placeHolder object is located at visibleChildren[0]. By using Binding, you can bind the placeholder's x position to a custom property (e.g., holderPadding). You can then change that property based on the TextField's activeFocus. The changes can be seen in the following code:

      TextField {
          id: input
          property alias iconSource: image.source
      
          property real spacing: 5
          property real holderPadding: activeFocus ? Material.textFieldHorizontalPadding : leftPadding
      
          Binding { target: input.visibleChildren[0]; property: 'x'; value: input.holderPadding }
          Behavior on holderPadding { NumberAnimation {} }
      
          selectByMouse: true
          height: 40; width: 250
          placeholderText: 'placeholder'
          leftPadding: image.x + image.width + spacing
      
          Image {
              id: image
              x: 10; y: (parent.height - height) / 2
              height: width; width: parent.height * 0.5
              source: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 483.1 482.7'%3E%3Cpath fill='%235f6469' d='M93 364.4c88.9-67.4 208-67 296.8 0 105-124.6 15.1-316.6-148.4-316.1C78.2 47.6-11 239.7 93 364.4zm148.4-99c-75.2 1.5-112.9-92.4-59.7-144.2 31.5-32.5 88.8-33.1 120.1 0 52.2 51.8 15.1 147-60.4 144.2zm0 217.2a239.3 239.3 0 0 1-222-147.2C-48.1 176.9 68.9.4 241.4 0A241.7 241.7 0 0 1 464 335.4c-36.9 87.7-126.3 148.9-222.6 147.2zm0-48.2a180 180 0 0 0 112.2-36.2A190.4 190.4 0 0 0 241.4 362c-41-.2-79.7 13-112.2 36.2 32.3 23.9 71 37.2 112.2 36.2zm0-217.2a35.1 35.1 0 0 0 36.2-36.2 35.1 35.1 0 0 0-36.2-36.2 35.3 35.3 0 0 0-36.2 36.2 35.1 35.1 0 0 0 36.2 36.2z'/%3E%3C/svg%3E"
              sourceSize{ width: 50; height: 50 }
          }
      }
      

    Note
    There are also some issues if you want to use it with RTL, which you can fix by adding some conditions.