As I mentioned in My First Experience with JavaFX blog, there is no password field in JavaFX, so I had to google for some workarounds. Although I found a few, none of them worked flawlessly, so last night I decided to spend some time trying to come up with a password field that would really work as expected. And, I think I managed to come up with an elegant and simple solution. No hacking in form of covering the text area with additional components or adding effects that blur the text box (including the caret and component borders). It looks and behaves exactly as you would expect of a password field. Click on the following picture to try it out:
Or click the following button for standalone mode:
And here is how it is implemented:
import javafx.scene.control.TextBox; import javafx.util.Math; /** * @author Martin Matula */ public class PasswordBox extends TextBox { public-read var password = ""; override function replaceSelection(arg) { var pos1 = Math.min(dot, mark); var pos2 = Math.max(dot, mark); password = "{password.substring(0, pos1)}{arg}{password.substring(pos2)}"; super.replaceSelection(getStars(arg.length())); } override function deleteNextChar() { if ((mark == dot) and (dot < password.length())) { password = "{password.substring(0, dot)}{password.substring(dot + 1)}"; } super.deleteNextChar(); } override function deletePreviousChar() { if ((mark == dot) and (dot > 0)) { password = "{password.substring(0, dot - 1)}{password.substring(dot)}"; } super.deletePreviousChar(); } function getStars(len: Integer): String { var result: String = ""; for (i in [1..len]) { result = "{result}*"; } result; } }
Quite simple, isn’t it? Here is how it is used in the Main class:
import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.text.Text; var password: PasswordBox; var stage: Stage = Stage { title: "PasswordBox Demo" width: 200 height: 100 scene: Scene { content: [ password = PasswordBox { translateX: 10 translateY: 10 columns: 20 }, Text { x: 10 y: 50 content: bind password.password } ] } }
I guess there is still a room for improving the API – the real password is stored in the password
property, while the text
property became useless and should never be set by a client. If you want to populate the field with a remembered password, you need to do it by calling replaceSelection("password")
on the password field after it’s initialization (rather than setting the text
or the password
properties). Anyway, I wanted to keep the code simple so that you can easily see the basic idea behind it.
Aah, a nice, simple implementation, with a smart usage of function overriding. Well done!
Note, I have an alternative implementation of getStars, using the fact that a sequence of strings in an interpolated variable is made of concatenation of the strings.
super.replaceSelection(“{for (i in [ 1 .. arg.length() ]) ‘*’}”);
Oh, nice – I did not know that. Thanks!
Hey Martin, very cool. Only thing I’d do differently is to have PasswordBox extend TextInputControl and use a TextBox in it’s skin implementation (delegation vs inheritance). One reason is that TextBox gains the ability to be multiline in the next realease which would be odd for a password box I guess.
One other thing, does this implementation work with promptText?
Hi Richard. I am glad you like it! :) Yes, it does work with promptText. And thanks for the hints. Will look at it.
I also believe that it should be possible to preset a password textfield with a value (“remember password”)
Yes, I said in the blog, you can do this by calling replaceSelection(“remembered password”). So it works, although could be made nicer.
Thank for share the code.
Thanks a lot for the code. Awesome implementation. Really useful
perfecto… muy bueno y bonito , me gusto mucho :) gracias… yo are el mio basándome en el tuyo gracias…
Thank you – It was very useful.
Thanks for share this.