Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
1

P: SDK: LrView.bind (key) fails when "key" contains a period

LEGEND ,
Jan 16, 2022 Jan 16, 2022

LrView.bind (key) creates a non-working binding when "key" contains a period. To reproduce, install this plugin:

https://www.dropbox.com/s/fhx9521z9re47ro/bind-bug.lrdevplugin.2022.01.16.zip?dl=0 

 

and then do File > Plug-in Extras > Bind Bug. That will produce this window:

johnrellis_0-1642390331924.png

 

The first checkbox should be unchecked (white), not in the mixed state (hyphen).  Tested on LR 11.1 / Mac OS 11.6.2.

 

Just one more SDK bug to work around.

 

Here's the script for the Bind Bug command:

 

local LrBinding = import "LrBinding"
local LrDialogs = import "LrDialogs"
local LrFunctionContext = import "LrFunctionContext"
local LrView = import "LrView"

local bind = LrView.bind
local f = LrView.osFactory()

local key1 = "x.y"
local key2 = "x_y"

LrFunctionContext.callWithContext ("main", function (context)
    local prop = LrBinding.makePropertyTable (context)
    prop [key1], prop [key2] = false, false
    assert (prop [key1] == false)
    assert (prop [key2] == false)
    LrDialogs.presentModalDialog {title = "Bind Bug", 
        contents = f:column {bind_to_object = prop,
            f:checkbox {title = "Key " .. key1, value = bind (key1)},
            f:checkbox {title = "Key " .. key2, value = bind (key2)}}}
    end)

 

 

Bug Fixed
TOPICS
macOS , SDK
1.8K
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 2 Correct answers

Adobe Employee , Jun 14, 2022 Jun 14, 2022

Greetings,

 

The SDK will be updated later this week (June 13). The update contains a fix for this issue.  Please download and the latest version of your application via the Adobe Creative Cloud Application or your respective device app store.

If you do not see the update (Mac and Win) you can refresh your Creative Cloud App with the keyboard shortcut [Ctrl/Cmd]+[Alt/Opt]+[ R ]. 

 

Note: App store availability can take several days for the update to appear and be available. 

 

Thank you for your

...
Status Fixed
Translate
Adobe Employee , Jan 17, 2022 Jan 17, 2022

Setting status - adding bug number

Status Started
Translate
20 Comments
Adobe Employee ,
Jan 17, 2022 Jan 17, 2022

Setting status - adding bug number

Rikk Flohr: Adobe Photography Org
Status Started
Translate
Report
Adobe Employee ,
Jun 14, 2022 Jun 14, 2022

Greetings,

 

The SDK will be updated later this week (June 13). The update contains a fix for this issue.  Please download and the latest version of your application via the Adobe Creative Cloud Application or your respective device app store.

If you do not see the update (Mac and Win) you can refresh your Creative Cloud App with the keyboard shortcut [Ctrl/Cmd]+[Alt/Opt]+[ R ]. 

 

Note: App store availability can take several days for the update to appear and be available. 

 

Thank you for your patience.

Rikk Flohr: Adobe Photography Org
Status Fixed
Translate
Report
LEGEND ,
Jun 17, 2022 Jun 17, 2022

This was only partially fixed. The behavior is "as designed", but it was (and still is) undocumented in the API Reference for LrView.bind().

 

The Lightroom Classic Programmers Guide has been updated on page 98 with this sentence:

 

"The keys in the table are treated as “.” (dot/period) separated hierarchical key-value pairs, e.g. a key such as x.y would bind with the key y in the table bound to key x."

 

The API Reference documentation for LrView.bind() should be similarly updated:

 

"key (string) A key name for the observed property. If the key is of the form x.y, it references to the key y in the table bound to key x."

 

Here's a modified bind-bug.lua for the plugin above that illustrates the behavior:

 

 

local LrBinding = import "LrBinding"
local LrDialogs = import "LrDialogs"
local LrFunctionContext = import "LrFunctionContext"
local LrView = import "LrView"

local bind = LrView.bind
local f = LrView.osFactory()

LrFunctionContext.callWithContext ("main", function (context)
    local prop = LrBinding.makePropertyTable (context)
    prop ["x.y"], prop.y, prop.z = false, false, {a = false}
    LrDialogs.presentModalDialog {title = "Bind Bug", 
        contents = f:column {bind_to_object = prop,
            f:checkbox {title = "Key x.y", value = bind "x.y"},
            f:checkbox {title = "Key y", value = bind "y"},
            f:checkbox {title = "Key z.a", value = bind "z.a"}}}
    assert (prop.z.a == true)
    assert (prop.y == true)
    assert (prop.x == true)
    end)

 

Translate
Report
Enthusiast ,
Sep 24, 2025 Sep 24, 2025

The SDK documentation is quite poor containing many errors and possibly omissions too.

 

It would make sense to me for the Checkbox control to have a handler for a clicked or changed event, similar to the Action associated with the Button control.

 

Does anyone know how a plugin can 'know' when a Checkbox's state is changed?

 

Translate
Report
LEGEND ,
Sep 24, 2025 Sep 24, 2025

You can use an observer function for the data binding of the checkbox, e.g.:

 

local prop = LrBinding.makePropertyTable (context)
prop:addObserver ("checkbox", function () ... end)
...
f:checkbox {title = "My checkbox", value = bind "checkbox"}

 

Translate
Report
Enthusiast ,
Sep 26, 2025 Sep 26, 2025

I've tried, but the code snipet provided by @johnrellis and those in the SDK are too vague.

 

John, I'm assuming the "checkbox" is a property in the _prop_ table which is bound to the checkbox titled "My checkbox" in you snipet. I tried doing similar in the PluginManager TopSection. It didn't work; the function associated with the addObserver didn't get called when the 'associated' checkbox's state changed.

 

Translate
Report
LEGEND ,
Sep 26, 2025 Sep 26, 2025

In views returned by Info.LrPluginInfoProvider, you need to set the the property "bind_to_object" to the property table. (The SDK Guide is ambiguous about this.)  Here's the code snippet from my Any Vision plugin:

sectionsForTopOfDialog = function (f, prop)
    return {
        {title = "Settings",
         f:row {bind_to_object = prop,
            f:checkbox {value = bind "smallDisplay", 
                title = "Use Any Vision on small displays"}},

 

Also, see this thread:

https://community.adobe.com/t5/lightroom-classic-discussions/trying-to-persist-a-dialog-value-using-...

Translate
Report
Enthusiast ,
Sep 26, 2025 Sep 26, 2025

@johnrellis 

 

Here is the start of my PluginManager for the top section

 

function PluginManager.sectionsForTopOfDialog( f, p )

  return {
    {
      bind_to_object = p,

      title = My Plugin Name",

      f:row {
        f:checkbox {
          title = Show update completed message",
          checked_value = true,
          unchecked_value = false,
          value = LrView.bind( 'ShowUpdateCompleted' ),
        },
      },

It is essentially the same as your code except the binding to my properties table p is placed at the very start so that the binding applies to every element in the section. I am initialising p in the PluginManager.startDialog function.

 

I have finally got something working with an observer function being fired on a checkbox change of state bound to a boolean item in my table p. However, I have a table within my prefs which has items bound to a group of checkboxes. My properties table p has an item named group, which is a table of tables; each table has a distinct name, such as first_option which is a table with one item enabled = true/false. Trying to do this

p:addObserver( 'group.first_option.enabled', checkboxChanged )

doesn't seem to work even though a checkbox's value can be bound to group.first_option.enabled with

value = LrView.bind( 'group.first_option.enabled' ),

I might have to revise my pluginPrefs structure.

 

Thanks for you help John.

Translate
Report
LEGEND ,
Sep 26, 2025 Sep 26, 2025

"the binding to my properties table p is placed at the very start"

 

Your code is setting the "bind_to_object" property in a section entry (as described on page 34 or the SDK Guide).  But it must instead be set within a view object, e.g. f:row() or f:checkbox (), and it appies to all hierarchically contained view objects.

Translate
Report
Enthusiast ,
Sep 26, 2025 Sep 26, 2025

@johnrellis 

 

"Your code is setting the "bind_to_object" property in a section entry"

 

I know. That is my intention. I changed it slightly, adding a f:view { bind_to_object = p, {...} } to encapsulate everything. I also flattened by pluginPrefs, so now everything works as expected. However, I should not have to do this and believe there are bugs here.

 

The problem is when my pluginPrefs uses a table item to group related options. For example, using this code

function PluginManager.sectionsForTopOfDialog( f, p )
    p['group'] = {}
    p.group['option_1'] = true
    p['option_2'] = true
    
    p:addObserver( 'group.option_1', onChangeHandler )
    p:addObserver( 'option_2', onChangeHandler )

    return {
        {
            title = 'Simple Test',

            f:view {
                bind_to_object = p,
            
                f:checkbox {
                    title = 'Option 1',
                    value = LrView.bind( 'group.option_1' ),
                    enabled = LrView.bind( 'option_2' ),
                },
                
                f:checkbox {
                    title = 'Option 2',
                    value = LrView.bind( 'option_2' ),
                    enabled = LrView.bind( 'group.option_1' ),
                },
            },
        },
    }
end

 

"Option 1" checkbox's value is intialised correctly from p.group.option_1, but clicking it fails to trigger the onChangeHandler observer function, which is not expected. When "Option1" is unchecked, "Option 2" does not get disabled, which is also not expected.

 

"Option 2" checkbox's value is initialised correctly from p.option_2. When clicked, the onChangeHandler observer function is triggered as expected. When it is unckecked, "Option 1" checkbox is disabled as expected.

 

So, it seems to me that there are some bugs here in the SDK:

 

value = LrView.bind( 'group.option' ) works

enabled = LrView.bind( 'group.option' ) does not work

visible = LrView.bind( 'group.option' ) does not work

p:addObserver( 'group.option', checkboxChanged ) does not work

 

There may be more that don't work when trying to bind items from a table entry in the observer table.

 

Translate
Report
LEGEND ,
Sep 27, 2025 Sep 27, 2025

I had to dig up my notes from several years ago, when I filed a bug report on the then-undocumented behavior of hierarchical dot notation in LrView.bind(). Adobe's response to that bug report was to modify the description on page 98 of the Lightroom Classic SDK Guide, but they never updated the API reference for LrView.bind(). The Guide now reads:

 

"The required argument of LrView.bind() is the key name; by default, this is in the table that is already

bound to the UI element; that is, the value of bind_to_object in the same UI element. This is inherited in

the view hierarchy, but can be overridden at any level. The keys in the table are treated as “.” (dot/period)

separated hierarchical key-value pairs, e.g. a key such as x.y would bind with the key y in the table bound

to key x."

 

Note that only LrView.bind() understands the hierarchical dot notation for keys.  Indexing property tables and the addObserver() method do not recognize it.  And if you want nested properties recognized by addObserver(), the subtables also have to be property tables constructed by LrBinding.makePropertyTable().

 

With this in mind, I've attached a script "bind-nested.txt" that shows how to use nested property tables with the "value", "enabled", and "visible" properties of checkboxes and the corresponding observer functions. Save the script to the Scripts subfolder and restart LR; then run the script from the Scripts menu.

 

See the attached screen recording of the script in action.

 

 

 

 

Translate
Report
LEGEND ,
Sep 27, 2025 Sep 27, 2025
Translate
Report
Enthusiast ,
Sep 28, 2025 Sep 28, 2025

@johnrellis 

 

The problem with using 'x.y' as a name in a table is a separate issue and although allowed in Lua, it is very bad programming practice.

 

I continued testing with my own script and find that there are still problems with binding. If p is the base observable table and p.group is also set as an observable table with an entry p.group['a'] = something, then value = bind(  'a', 'p.group' ) doesn't initialise the control if the entire section is bound to p.

 

I'll provide my test script tomorrow which will explain and demonstrate the problems more clearly..

 

Translate
Report
LEGEND ,
Sep 28, 2025 Sep 28, 2025

"value = bind(  'a', 'p.group' )"

 

I don't see a two-argument form for LrView.bind() documented in the AP Reference or the SDK Guide, and it doesn't work in my testing either. However, I did observe that these other three methods did work for referencing a second property table in an LrView control hierarchy:

 

- bind {key = "key", bind_to_object = prop2}

 

- bind "prop2.key"

 

- Setting the property "bind_to_object = prop2" in a nested part of the control hierarchy.

 

This script illustrates all three methods:

local LrBinding = import "LrBinding"
local LrDialogs = import "LrDialogs"
local LrFunctionContext = import "LrFunctionContext"
local LrView = import "LrView"

local bind = LrView.bind
local f = LrView.osFactory()

LrFunctionContext.callWithContext ("main", function (context)
    local prop1 = LrBinding.makePropertyTable (context)
    prop1.prop2 = LrBinding.makePropertyTable (context)
    prop1.value, prop1.prop2.value = "prop1 value", "prop2 value"

    LrDialogs.presentModalDialog {title = "Bind", 
        contents = f:column {bind_to_object = prop1,
            spacing = f:control_spacing (),
            f:static_text {title = bind "value"},
            f:static_text {title = bind "prop2.value"},
            f:static_text {title = bind "value", 
                bind_to_object = prop1.prop2},
            f:column {bind_to_object = prop1.prop2,
                f:static_text {title = bind "value"}},
            f:static_text {title = bind {key = "value", 
                bind_to_object = prop1.prop2}}}}
    end)
Translate
Report
Enthusiast ,
Sep 28, 2025 Sep 28, 2025

@johnrellis 

 

"value = bind(  'a', 'p.group' )"

 

My mistake (misreading of the SDK)

 

Still, using the table argument LrView.bind { key = 'item', bind_to_object = 'props' } doesn't seem to work in this example script.

 

local LrFunctionContext = import 'LrFunctionContext'
local LrDialogs = import 'LrDialogs'
local LrBinding = import 'LrBinding'
local LrView = import 'LrView'


local function onChangeHandler( p, key, value )
  LrDialogs.showBezel( string.format ( 'Control having key %s is set to %s', key, value ), 1 )
end


LrFunctionContext.callWithContext( 'Control Bindings Test', function( context )
  local p = LrBinding.makePropertyTable( context )

  p.group = LrBinding.makePropertyTable( context )
  p.group['option1'], p.group['option2'], p['option3'] = true, false, true

  p.group:addObserver( 'option1', onChangeHandler )
  p.group:addObserver( 'option2', onChangeHandler )
  p:addObserver( 'option3', onChangeHandler )

  local bind = LrView.bind
  local f = LrView.osFactory()

  LrDialogs.presentModalDialog( {
    title = "Control Binding Test",
    cancelVerb = "< exclude >",
    contents = f:view {
      -- set default global binding
      bind_to_object = p,

      f:column {
        spacing = f:control_spacing(),

        f:row {
          f:column {
            spacing = f:control_spacing(),

            f:checkbox {
              title = "Option 1",
              value = bind { key = 'option1', bind_to_object = 'p.group' },
              enabled = bind { key = 'option2', bind_to_object = 'p.group' },
            },

            f:checkbox {
              title = "Option 2: Uncheck to disable Options 1 && 3 (doesn't work)",
              value = bind { key = 'option2', bind_to_object = 'p.group' },
              enabled = bind { key = 'option3', bind_to_object = 'p' },
            },
          },
        },

        f:row {
          f:checkbox {
            title = "Option 3: Uncheck to disable Option 2 (doesn't work)",
            value = bind 'option3',
            enabled = bind { key = 'option2', bind_to_object = 'p.group' },
          },
        },
      },
    },
  } )
end )

 

On running the script:

Options 1 and 2 don't get their values initialised properly, but Option 3 does

Unchecking Option 2 should disable Options 1 & 3, but it doesn't

Unchecking Option 3 should disable Option 2, but it doesn't

Changing the state of any of the checkboxes should trigger the observer function, but only Option 3 works

 

 

 

Translate
Report
Enthusiast ,
Sep 28, 2025 Sep 28, 2025

@johnrellis 

 

If I change the script to this

 

local LrFunctionContext = import 'LrFunctionContext'
local LrDialogs = import 'LrDialogs'
local LrBinding = import 'LrBinding'
local LrView = import 'LrView'


local function onChangeHandler( p, key, value )
  LrDialogs.showBezel( string.format ( 'Control having key %s is set to %s', key, value ), 1 )
end


LrFunctionContext.callWithContext( 'Control Bindings Test', function( context )
  local p = LrBinding.makePropertyTable( context )

  p.group = LrBinding.makePropertyTable( context )
  p.group['option1'], p.group['option2'], p['option3'] = true, false, true

  p.group:addObserver( 'option1', onChangeHandler )
  p.group:addObserver( 'option2', onChangeHandler )
  p:addObserver( 'option3', onChangeHandler )

  local bind = LrView.bind
  local f = LrView.osFactory()

  LrDialogs.presentModalDialog( {
    title = "Control Binding Test",
    cancelVerb = "< exclude >",
    contents = f:view {
      -- set default global binding
      bind_to_object = p,

      f:column {
        spacing = f:control_spacing(),

        f:row {
          f:column {
            spacing = f:control_spacing(),
            bind_to_object = p.group,

            f:checkbox {
              title = "Option 1",
--              value = bind { key = 'option1', bind_to_object = 'p.group' },
              value = bind 'option1',
--              enabled = bind { key = 'option2', bind_to_object = 'p.group' },
              enabled = bind 'option2',
            },

            f:checkbox {
              title = "Option 2: Uncheck to disable Options 1 && 3 (doesn't work)",
--              value = bind { key = 'option2', bind_to_object = 'p.group' },
              value = bind 'option2',
              enabled = bind { key = 'option3', bind_to_object = 'p' },
            },
          },
        },

        f:row {
          f:checkbox {
            title = "Option 3: Uncheck to disable Option 2 (doesn't work)",
            value = bind 'option3',
            enabled = bind { key = 'option2', bind_to_object = 'p.group' },
          },
        },
      },
    },
  } )
end )

 

then all Options get initialised correctly; unchecking Option 2 disables Option 1, but not Option 3; unchecking Option 3 still doesn't disable Option 2; all Options trigger the observer handler.

 

So, bindings still don't work as expected.

Translate
Report
LEGEND ,
Sep 28, 2025 Sep 28, 2025

This script is setting the "bind_to_object" property of the various controls to strings rather than property tables:

bind_to_object = 'p.group' 
bind_to_object = 'p'

 When I change those to:

bind_to_object = p.group
bind_to_object = p

your first script appears to work as intended.

Translate
Report
Enthusiast ,
Sep 28, 2025 Sep 28, 2025

@johnrellis 

 

This where the SDK and the Programmers Guide is so misleading and confusing. Page 98 of the Programmers Guide:

 

"You can override the bound table for a specific binding by passing the LrView.bind() function a table
containing both the key and the table it comes from:


visible = LrView.bind { key = "mySetting", bind_to_object = "myTable" }


This allows you to bind different properties in one view object to keys in different tables.

 

The bind_to_object is being assigned a string! It also needs to be very clear that the object being bound is an observable table created with LrBinding.makePropertyTable, not just an ordinary lua table.

 

At last, an answer that actually makes sense and as a bonus, it works too.

 

Thanks for all your help and knowledge John. Most appreciated. Now I can move on with my plug-in development.

 

Regards, Tony

Translate
Report
LEGEND ,
Sep 28, 2025 Sep 28, 2025

"This where the SDK and the Programmers Guide is so misleading and confusing."

 

Agreed. Though the original bug report was marked as "fixed", the API Reference was never updated.  And the Guide has all sorts of little errors like this.

Translate
Report
Enthusiast ,
Sep 28, 2025 Sep 28, 2025
LATEST

From the SDK itself:

 

LrView.bind( binding )This namespace function declares a binding to a data value in a property table.

First supported in version 1.3 of the Lightroom SDK.

Parameters

1. binding (string or table) The data property. The name of a key in the default bound table, or, to specify a single property, a table with these entries:

  • key (string) A key name for the observed property.
  • bind_to_object (string) Optional. The name of an observable table containing this key, which overrides the current bound table.

 

bind_to_object type is a string! Wasted hours and hours because of this poor documentation.

 

Adobe really needs to take responsiblity for this and fix all the errors in their documentation.

 

Translate
Report