Skip to main content
Jongware
Community Expert
Community Expert
February 17, 2012
Answered

JS/ScriptUI: placeholder madness

  • February 17, 2012
  • 4 replies
  • 12594 views

I created this yummy text box entry placeholder for CS4 on my Mac:

but for some reason this fails to draw correctly on Windows! If the edit box is active and the cursor is in it, the text is not drawn but the cursor blinks at correct position at the end of that invisible text. Moving the cursor or typing in something makes the text pop back into view. (Sorry, should have thought of making a 'shot of that as well.) A real shame, because on my Mac it works as I meant it to: placeholder text shows when the field is inactive and empty, placeholder disappears when you enter the field and type anything in it. Delete text and leave box makes it appear again.

Is this an error in my code (silently corrected on the Mac but showing up on 'the other platform'), or is it an error on the Windows' Side of the world? Or is this just something one should not be messing with using ScriptUI? (A fairly reasonable explanation.) Or might it work correctly on CS5 for both platforms? (In which case I could put in a platform/version check -- "no fancy dialog for you, you Older Version on a Microsoft Platform!").

I also have a variant of same code that would underline static text, which also shows up correctly on Mac and not-at-all on Windows -- but then it may be failing for the very same reason.

My code:

var w = new Window ("dialog", "Display Placeholder");

w.bg = w.graphics.newBrush(w.graphics.BrushType.SOLID_COLOR,[1,1,1]);

w.textPen = w.graphics.newPen(w.graphics.PenType.SOLID_COLOR,[0.67,0.67,0.67], 1);

with(w.add ("group"))

{

  add ("statictext", undefined, "Text");

  editbox = add ("edittext", undefined, '');

  editbox.characters = 32;

  editbox.onDraw = function(d)

  {

    if( this.text || this.active)

      this.parent.onDraw(d)

    else

    {

      this.graphics.rectPath (0,0,this.size.width,this.size.height);

      this.graphics.fillPath(w.bg);

      this.graphics.drawString("<required>",w.textPen,0,0,this.graphics.font);

    }

  };

}

This topic has been closed for replies.
Correct answer Marc Autret

Hi Guys

Peter, I'm afraid I should disagree with you on a few points.

(...) In CS5.5, your script breaks on this.parent.onDraw(d). The error message is "this.parent.onDraw is not a function", which is fair enough, because onDraw is a callback. (...)

The reason why this.parent.onDraw is not a function in Jongware's code, is just that this.parent.onDraw is undefined—as John supposed. When we call x( ) from an undefined x var, the interpreter prompts a not-a-function error.

Why is this.parent.onDraw undefined? Because there is no line in the code which sets up such member on this.parent—which by the way is a (ScriptUI) Group object, considering the context. What we call 'callback' functions are optional custom methods that are supposed to connect to events, usually, but widget prototypes do not provide any default callbacks. myWidget.onDraw, myWidget.onClick, etc., are simply undefined as long as we don't set them.

In addition, the onDraw callback, when defined, does not work the same way that other event callbacks (which are high-level fake event listeners and could generally be developed through an addEventListener() setter providing much more precision). Indeed, onDraw has no actual event counterpart (or, if any, it is not exposed to scripters as 'show', 'focus', 'blur', 'change', 'click', etc., are.) As documented, onDraw is automatically called when a widget needs to be (re)drawn, but this 'when' is highly unpredictable as it depends on the user interactions, the OS, and probably a number of low-level events.

We also know that custom layout—i.e. myContainer.layout.layout(1)—triggers such drawing stages (from containers). Also, there are crazy paths to get a non-container widget redrawn, for example:

    wg.visible=false; wg.visible=true;

or sometimes:

   wg.size = [wg.size[0],wg.size[1]];

The issue with manually calling  a custom onDraw() method is we generally don't know when it is safe to call it and whether it may conflict with the automatic calls. I found circumstances where manual onDraw() simply fails due to a temporary invalidation of the widget.graphics property (I believe this depends on the object type). This may explain why Peter wrote:

(...) onDraw is a function but not one that you call like x.onDraw()

However, onDraw is indeed a simple (callable) function if the script did it so. What matters is that a custom onDraw() overrides and bypasses, as much as possible, the native drawing stage. So, in many cases, an empty onDraw() callback makes the widget totally undrawn (hidden) although room has been reserved to it during layout. (That's not true for composite or very interactive widgets—such as scrollbar or listbox—which still inherit from low-level painting events, at least partially.)

ANYWAY, in Jongware's script I don't see any reason to make this.onDraw() attempt to call a hypothetical parent onDraw method—which for the time being does not exist—or even to trigger the native drawing stage on the parent. Why? I guess there is a confusion between drawing and layout (?)

What is sure is that the statement  this.parent.onDraw(d) fails. And, since ScriptUI is known for hushing errors up in many event handlers, you get unaccountable side effects in your EditText control.

To me, a valid approach would be to invoke the ScriptUIGraphics.drawOSControl() method when you need to produce the default widget skin before further customization.

The following snippets should provide better results (unfortunately, I can't test this on all systems):

// =====================================

// EditText placeholder

// =====================================

var u,

    w = new Window('dialog', "Display Placeholder"),

    e1 = w.add('edittext'),

    e2 = w.add('edittext'),

    b = w.add('button', u, "OK"),

    // ---

    wgx = w.graphics,

    grayPen = wgx.newPen(wgx.PenType.SOLID_COLOR,[.67,.67,.67], 1);

e1.characters = e2.characters = 32;

e1.onDraw = e2.onDraw = function(/*DrawState*/)

{

    var gx = this.graphics;

    gx.drawOSControl();

    this.text || this.active || gx.drawString("<required>", grayPen, 0, 0);

};

w.show();

And:

// =====================================

// Underlined StaticText

// =====================================

var u,

    w = new Window('dialog', "Underline Me!"),

    s1 = w.add('statictext', u, "Not Underlined"),

    s2 = w.add('statictext', u, "Underlined"),

    b = w.add('button', u, "OK"),

    // ---

    wgx = w.graphics,

    linePen = wgx.newPen(wgx.PenType.SOLID_COLOR,[0,0,0], 1);

s2.preferredSize[1] += 3;

s2.onDraw = function(/*DrawState*/)

{

    var gx = this.graphics,

        sz = this.preferredSize,

        y = sz[1]-1;

    gx.drawOSControl();

    gx.newPath();

    gx.moveTo(0, y);

    gx.lineTo(sz[0],y);

    gx.strokePath(linePen);

};

w.show();

Hope that helps.

@+

Marc

4 replies

UQg
Legend
February 28, 2015

Can someone confirm that Marc's first snippet in the solution is still working fine in CC 2014 ?

For me it doesnt really work :

(1) the text is offset towards the bottom left

(2) only the part outside the text box is drawn !

Screenshots made in After Effects CC 2014 (broken) and ESTK CC 2014 (works as expected):

Xavier

Marc Autret
Legend
February 28, 2015

Hi Xavier,

I'm afraid drawOsControl() doesn't work anymore (as expected) in ScriptUI CC

…among other things

@+

Marc

UQg
Legend
February 28, 2015

Marc, thank you for fast answer.

This also explain why i had troubles with a function like

function drawImage(){

    if (this._image instanceof ScriptUIImage){

        this.graphics.drawImage(this._image, 0,0);

        }

    else{

        this.graphics.drawOSControl();

        };

    };

myDropDown.onDraw = drawImage : if the dropddown has a "_image" property, it was drawing the image as expected, else it was drawing nothing.

But i still have a problem with drawString(), completely unrelated to drawOSControl().

I'll open a new thread then, maybe tomorrow!

Xavier

Marc Autret
Marc AutretCorrect answer
Legend
February 19, 2012

Hi Guys

Peter, I'm afraid I should disagree with you on a few points.

(...) In CS5.5, your script breaks on this.parent.onDraw(d). The error message is "this.parent.onDraw is not a function", which is fair enough, because onDraw is a callback. (...)

The reason why this.parent.onDraw is not a function in Jongware's code, is just that this.parent.onDraw is undefined—as John supposed. When we call x( ) from an undefined x var, the interpreter prompts a not-a-function error.

Why is this.parent.onDraw undefined? Because there is no line in the code which sets up such member on this.parent—which by the way is a (ScriptUI) Group object, considering the context. What we call 'callback' functions are optional custom methods that are supposed to connect to events, usually, but widget prototypes do not provide any default callbacks. myWidget.onDraw, myWidget.onClick, etc., are simply undefined as long as we don't set them.

In addition, the onDraw callback, when defined, does not work the same way that other event callbacks (which are high-level fake event listeners and could generally be developed through an addEventListener() setter providing much more precision). Indeed, onDraw has no actual event counterpart (or, if any, it is not exposed to scripters as 'show', 'focus', 'blur', 'change', 'click', etc., are.) As documented, onDraw is automatically called when a widget needs to be (re)drawn, but this 'when' is highly unpredictable as it depends on the user interactions, the OS, and probably a number of low-level events.

We also know that custom layout—i.e. myContainer.layout.layout(1)—triggers such drawing stages (from containers). Also, there are crazy paths to get a non-container widget redrawn, for example:

    wg.visible=false; wg.visible=true;

or sometimes:

   wg.size = [wg.size[0],wg.size[1]];

The issue with manually calling  a custom onDraw() method is we generally don't know when it is safe to call it and whether it may conflict with the automatic calls. I found circumstances where manual onDraw() simply fails due to a temporary invalidation of the widget.graphics property (I believe this depends on the object type). This may explain why Peter wrote:

(...) onDraw is a function but not one that you call like x.onDraw()

However, onDraw is indeed a simple (callable) function if the script did it so. What matters is that a custom onDraw() overrides and bypasses, as much as possible, the native drawing stage. So, in many cases, an empty onDraw() callback makes the widget totally undrawn (hidden) although room has been reserved to it during layout. (That's not true for composite or very interactive widgets—such as scrollbar or listbox—which still inherit from low-level painting events, at least partially.)

ANYWAY, in Jongware's script I don't see any reason to make this.onDraw() attempt to call a hypothetical parent onDraw method—which for the time being does not exist—or even to trigger the native drawing stage on the parent. Why? I guess there is a confusion between drawing and layout (?)

What is sure is that the statement  this.parent.onDraw(d) fails. And, since ScriptUI is known for hushing errors up in many event handlers, you get unaccountable side effects in your EditText control.

To me, a valid approach would be to invoke the ScriptUIGraphics.drawOSControl() method when you need to produce the default widget skin before further customization.

The following snippets should provide better results (unfortunately, I can't test this on all systems):

// =====================================

// EditText placeholder

// =====================================

var u,

    w = new Window('dialog', "Display Placeholder"),

    e1 = w.add('edittext'),

    e2 = w.add('edittext'),

    b = w.add('button', u, "OK"),

    // ---

    wgx = w.graphics,

    grayPen = wgx.newPen(wgx.PenType.SOLID_COLOR,[.67,.67,.67], 1);

e1.characters = e2.characters = 32;

e1.onDraw = e2.onDraw = function(/*DrawState*/)

{

    var gx = this.graphics;

    gx.drawOSControl();

    this.text || this.active || gx.drawString("<required>", grayPen, 0, 0);

};

w.show();

And:

// =====================================

// Underlined StaticText

// =====================================

var u,

    w = new Window('dialog', "Underline Me!"),

    s1 = w.add('statictext', u, "Not Underlined"),

    s2 = w.add('statictext', u, "Underlined"),

    b = w.add('button', u, "OK"),

    // ---

    wgx = w.graphics,

    linePen = wgx.newPen(wgx.PenType.SOLID_COLOR,[0,0,0], 1);

s2.preferredSize[1] += 3;

s2.onDraw = function(/*DrawState*/)

{

    var gx = this.graphics,

        sz = this.preferredSize,

        y = sz[1]-1;

    gx.drawOSControl();

    gx.newPath();

    gx.moveTo(0, y);

    gx.lineTo(sz[0],y);

    gx.strokePath(linePen);

};

w.show();

Hope that helps.

@+

Marc

Peter Kahrel
Community Expert
Community Expert
February 19, 2012

Well, you live and learn. Thanks for your exposé, Marc, that cleared up things. And thanks John, for your observations.

Peter

Peter Kahrel
Community Expert
Community Expert
February 18, 2012

In CS5.5, your script breaks on this.parent.onDraw(d). The error message is "this.parent.onDraw is not a function", which is fair enough, because onDraw is a callback. So the question is not "Why does it not work on Windows?", but rather, "Why does it work on a Mac?". When I take out that line the script shows exactly the behaviour you describe. Looks like a bug to me.

On Windows there are some other display problems with edittext controls, but their solution, using this.layout.layout(), wouldn't work in your script. Can you not do something with editbox.onActivate and editbox.onDeactivate? After all, you want the contents to change when the user activates another control, not every time the mouse cursor moves away from the edit field.

Peter

John Hawkinson
Inspiring
February 18, 2012

The error message is "this.parent.onDraw is not a function", which is fair enough, because onDraw is a callback.

What? How is a callback not a function?

I would think you ought to check that the callback is defined (it might be null or undefined) before trying to call it, but I don't see why it shouldn't be callable.

Jongware: You are also missing a semicolon from that line...

Peter Kahrel
Community Expert
Community Expert
February 18, 2012

onDraw is a function but not one that you call like x.onDraw(). But you're right, that error message isn't precise.

Jongware
Community Expert
JongwareCommunity ExpertAuthor
Community Expert
February 17, 2012

(Huh, I belately realize to see the correct/wrong behavior you have to add a second dialog element to that window. Insert

add ("button", undefined, "OK")

above the final closing brace. You also might want to add

w.show();

at the very bottom to ensure proper testing can commence.