NSSlider and arrangedObjects

  • Hi everyone,
    I am attempting to bind an NSSlider to a property of every object in an NSArrayController, rather than just the current selection. There are other properties that are bound to the selection, but this particular one I want to change for everything, regardless. The array managed by the controller is initially empty when the program starts.

    Binding the NSSlider's value to the NSArrayController's "arrangedObjects.propertyName" causes my program to get a SIGABRT on opening, with the following uncaught exception:
    "Cannot create double from object ( ) of class __NSArray0"

    I assume this is to do with the empty array. I have tried binding the NSSlider's enable to "<arrangedObjects....>" and "arrangedObjects.canRemove". Neither fixes the problem. If I bind the value to "selection.propertyName", the enabling/disabling works as expected (i.e. the slider is greyed out until the array has at least one entry). Hence I am confused.

    Thanks in advance if you can help,
    Toby
  • On Jan 19, 2012, at 03:03 , Tobias Wood wrote:

    > I am attempting to bind an NSSlider to a property of every object in an NSArrayController, rather than just the current selection. There are other properties that are bound to the selection, but this particular one I want to change for everything, regardless. The array managed by the controller is initially empty when the program starts.

    It's not entirely clear what you mean. Are you saying you want to have a single slider in your window, that somehow represents all the array elements? If so, what value would the slider show when the array elements have different values for the relevant property? Is there a table view involved in this somewhere?

    From the information you've provided, the answer is probably that you can't do what you want with bindings. One possible alternative is to add a new property to your data model, that represents the "combined" property values of the array (whatever that means), and bind the slider to this property. The property getter and setter would then be responsible for co-ordinating the combined value with the individual values.

    > Binding the NSSlider's value to the NSArrayController's "arrangedObjects.propertyName" causes my program to get a SIGABRT on opening, with the following uncaught exception:
    > "Cannot create double from object ( ) of class __NSArray0"
    >
    > I assume this is to do with the empty array.

    No, it's because you can't bind through a key path that contains a collection property ("arrangedObjects" in this case). This situation is complicated by the fact that NSArray responds to a selector it doesn't recognize (such as your "propertyName") by returning the result of sending this selector to every element in the array. This array result isn't convertible to the slider's numeric value by any standard KVC mechanism, so you get the exception.

    > I have tried binding the NSSlider's enable to "<arrangedObjects....>" and "arrangedObjects.canRemove". Neither fixes the problem. If I bind the value to "selection.propertyName", the enabling/disabling works as expected (i.e. the slider is greyed out until the array has at least one entry). Hence I am confused.

    There's an important difference between "selection" and "arrangedObjects". The latter is an array, but "selection" is a proxy object that represents either a single object from the controller (*the* selected object), or a special multiple-selection marker, or a special invalid value marker, or a special no-value marker. The special markers are used (for example) for making text fields show "multiple selection" or "no value" messages, and for automatically enabling/disabling controls as you noted.

    Again, there's a complication. Table view *column* bindings are special in that they appear to be able to bind through collection objects, but in fact their key paths are treated as 2 separate parts by the bindings mechanism. The first part allows the column to find the array of values for all table rows, and to choose the correct value per row based on the row index; the second part allows the column to find the actual value to display.
  • On Jan 21, 2012, at 1:00 AM, Quincey Morris wrote:

    > On Jan 19, 2012, at 03:03 , Tobias Wood wrote:
    >
    >> Binding the NSSlider's value to the NSArrayController's "arrangedObjects.propertyName" causes my program to get a SIGABRT on opening, with the following uncaught exception:
    >> "Cannot create double from object ( ) of class __NSArray0"
    >>
    >> I assume this is to do with the empty array.
    >
    > No, it's because you can't bind through a key path that contains a collection property ("arrangedObjects" in this case).

    Not to get too deep into the weeds, but arrangedObjects is, like selection, also a proxy and it does allow binding through it.  That's a big part of its purpose/value.

    > This situation is complicated by the fact that NSArray responds to a selector it doesn't recognize (such as your "propertyName") by returning the result of sending this selector to every element in the array.

    Um, not quite.  This isn't about not responding to a selector, it's about its implementation of -valueForKey:.  NSArray implements -valueForKey: by returning an array of the results of invoking -valueForKey: with the same key argument on each of its elements.

    > This array result isn't convertible to the slider's numeric value by any standard KVC mechanism, so you get the exception.

    That's the meat of the issue.

    >> I have tried binding the NSSlider's enable to "<arrangedObjects....>" and "arrangedObjects.canRemove". Neither fixes the problem. If I bind the value to "selection.propertyName", the enabling/disabling works as expected (i.e. the slider is greyed out until the array has at least one entry). Hence I am confused.
    >
    > There's an important difference between "selection" and "arrangedObjects". The latter is an array, but "selection" is a proxy object that represents either a single object from the controller (*the* selected object), or a special multiple-selection marker, or a special invalid value marker, or a special no-value marker. The special markers are used (for example) for making text fields show "multiple selection" or "no value" messages, and for automatically enabling/disabling controls as you noted.

    You correctly diagnosed that the exception was due to the inability to make a double from the array.  That's the whole of the necessary explanation for the symptom.  Tobias had simply made an incorrect assumption that it was due to the array being empty, but neither that nor the no-selection marker from the selection property are relevant.

    It is true that the automatic enabling/disabling when bound to selection.propertyName is due to the selection's markers and the Conditionally Sets Enabled bindings option (which is on by default).  But that doesn't resolve the confusion about why it was throwing an exception in some cases and not others.

    > Again, there's a complication. Table view *column* bindings are special in that they appear to be able to bind through collection objects, but in fact their key paths are treated as 2 separate parts by the bindings mechanism. The first part allows the column to find the array of values for all table rows, and to choose the correct value per row based on the row index; the second part allows the column to find the actual value to display.

    Table columns do have special handling for array-valued bindings.  It's that they distribute the array values among their rows.  Table columns are not special in being able to bind through collection properties.  That's a feature of NSArrayController.

    The table column's bindings still have just an observed object (often the array controller) and a single key path (often of the form "arrangedObject.propertyName").  It's just that, when they receive an array from [observedObject valueForKeyPath:observedKeyPath], they know to pick one element from that array for each row.

    > It's not entirely clear what you mean. Are you saying you want to have a single slider in your window, that somehow represents all the array elements? If so, what value would the slider show when the array elements have different values for the relevant property? Is there a table view involved in this somewhere?

    Yes, these are the important questions.  Tobias, what do you actually intend the slider to signify?  What should happen when its value is adjusted by the user?

    Regards,
    Ken
  • Thanks for the responses guys, I think I follow most of it.

    My documents for this app are essentially movies (4D images, the 4th dimension is time). I want this particular slider to set the timepoint for all open documents, in a 'write only' fashion. Currently there is no UI to set the timepoints individually, and each document carries internal logic to prevent setting a timepoint beyond the end of the file.

    Currently I have implemented this behaviour by binding the slider to a property on my document controller, and the setter for this forwards the desired timepoint onto all open documents. I was hoping to replace this with a binding just to eliminate some glue code.

    I thought it would be simple because I have another slider which is bound to the 'contrast' property of the selection from the array controller, and this behaves exactly as I want it to (the selection is controlled via a table view, and the slider will set the contrast of all selected rows).

    I hope the above is clear. Is keeping the property on the document controller likely the simplest way forward?

    Toby

    On 21 Jan 2012, at 08:54, Ken Thomases <ken...> wrote:

    > On Jan 21, 2012, at 1:00 AM, Quincey Morris wrote:
    >
    >> On Jan 19, 2012, at 03:03 , Tobias Wood wrote:
    >>
    >>> Binding the NSSlider's value to the NSArrayController's "arrangedObjects.propertyName" causes my program to get a SIGABRT on opening, with the following uncaught exception:
    >>> "Cannot create double from object ( ) of class __NSArray0"
    >>>
    >>> I assume this is to do with the empty array.
    >>
    >> No, it's because you can't bind through a key path that contains a collection property ("arrangedObjects" in this case).
    >
    > Not to get too deep into the weeds, but arrangedObjects is, like selection, also a proxy and it does allow binding through it.  That's a big part of its purpose/value.
    >
    >> This situation is complicated by the fact that NSArray responds to a selector it doesn't recognize (such as your "propertyName") by returning the result of sending this selector to every element in the array.
    >
    > Um, not quite.  This isn't about not responding to a selector, it's about its implementation of -valueForKey:.  NSArray implements -valueForKey: by returning an array of the results of invoking -valueForKey: with the same key argument on each of its elements.
    >
    >> This array result isn't convertible to the slider's numeric value by any standard KVC mechanism, so you get the exception.
    >
    > That's the meat of the issue.
    >
    >
    >>> I have tried binding the NSSlider's enable to "<arrangedObjects....>" and "arrangedObjects.canRemove". Neither fixes the problem. If I bind the value to "selection.propertyName", the enabling/disabling works as expected (i.e. the slider is greyed out until the array has at least one entry). Hence I am confused.
    >>
    >> There's an important difference between "selection" and "arrangedObjects". The latter is an array, but "selection" is a proxy object that represents either a single object from the controller (*the* selected object), or a special multiple-selection marker, or a special invalid value marker, or a special no-value marker. The special markers are used (for example) for making text fields show "multiple selection" or "no value" messages, and for automatically enabling/disabling controls as you noted.
    >
    > You correctly diagnosed that the exception was due to the inability to make a double from the array.  That's the whole of the necessary explanation for the symptom.  Tobias had simply made an incorrect assumption that it was due to the array being empty, but neither that nor the no-selection marker from the selection property are relevant.
    >
    > It is true that the automatic enabling/disabling when bound to selection.propertyName is due to the selection's markers and the Conditionally Sets Enabled bindings option (which is on by default).  But that doesn't resolve the confusion about why it was throwing an exception in some cases and not others.
    >
    >
    >> Again, there's a complication. Table view *column* bindings are special in that they appear to be able to bind through collection objects, but in fact their key paths are treated as 2 separate parts by the bindings mechanism. The first part allows the column to find the array of values for all table rows, and to choose the correct value per row based on the row index; the second part allows the column to find the actual value to display.
    >
    > Table columns do have special handling for array-valued bindings.  It's that they distribute the array values among their rows.  Table columns are not special in being able to bind through collection properties.  That's a feature of NSArrayController.
    >
    > The table column's bindings still have just an observed object (often the array controller) and a single key path (often of the form "arrangedObject.propertyName").  It's just that, when they receive an array from [observedObject valueForKeyPath:observedKeyPath], they know to pick one element from that array for each row.
    >
    >
    >> It's not entirely clear what you mean. Are you saying you want to have a single slider in your window, that somehow represents all the array elements? If so, what value would the slider show when the array elements have different values for the relevant property? Is there a table view involved in this somewhere?
    >
    > Yes, these are the important questions.  Tobias, what do you actually intend the slider to signify?  What should happen when its value is adjusted by the user?
    >
    > Regards,
    > Ken
    >
  • On Jan 21, 2012, at 00:54 , Ken Thomases wrote:

    > Table columns do have special handling for array-valued bindings.  It's that they distribute the array values among their rows.  Table columns are not special in being able to bind through collection properties.  That's a feature of NSArrayController.
    >
    > The table column's bindings still have just an observed object (often the array controller) and a single key path (often of the form "arrangedObject.propertyName").  It's just that, when they receive an array from [observedObject valueForKeyPath:observedKeyPath], they know to pick one element from that array for each row.

    I have to speak under potential correction, since I don't have the implementations to refer to, but I don't believe this analysis.

    Typically, nothing can "bind through collection properties", because typically a binding involves (along with other things) an observation of the target object on a key path, and trying to set up such an observation will throw an exception. If the "collection property" is not really a collection but a proxy (as you've described "arrangedObjects" to be), then the exception might be avoided, but the resulting observation will be useless because there's no KVO compliance. Specifically, I mean that if the key path is "arrangedObjects.propertyName", propertyName changes to individual objects won't propagate to an observer of the key path, at least not unless the arrangedObjects proxy itself observes every element of the underlying array, which is too horrendous to contemplate.

    In fact, it seems to me that logic dictates that table columns bound to something we normally write as "arrangedObjects.propertyName" do *not* observe on that key path. Again I don't know, but my assumption has always been that table columns observe the propertyName attributes of just the objects for rows that are currently visible. That in itself makes table columns "special", and their binding's so-called key path not really a key path.

    Furthermore, it also seems to me that logic rules against any idea of the table column implementation *requiring* the construction of the *entire* array of row objects, because table views are virtual in the sense that they only reference the objects they need from moment to moment. Anything else would imply horrendous performance penalties for table views with thousands or millions of rows, but I don't believe there is any such penalty implicit in NSTableView itself.

    Putting this another way, I find it hard to believe that table columns ever evaluate '[observedObject valueForKeyPath:observedKeyPath]', because that would seem to require construction of a potentially huge and costly array. I've always assumed that, assuming a so-called key path of the form "someArray.someProperty", table columns only ever evaluate '[[someArray objectAtIndex: rowIndex] valueForKey: someProperty]' for specific values of 'rowIndex', never '[[someArray valueForKey: someProperty] objectAtIndex: rowIndex]'.

    Finally, we already know, from discussions on this list in the past, that (given the same key path) there *is* a semantic difference between table column bindings and other bindings to arrays, such as those supplying arrays of menu item titles to NSPopUpMenu. In that case, "arrangedObjects.propertyName" fails to be useful because it indeed behaves like a typical key path in a typical binding, rather than having the special binding behavior of table columns.
  • On Jan 21, 2012, at 6:37 PM, Quincey Morris wrote:

    > On Jan 21, 2012, at 00:54 , Ken Thomases wrote:
    >
    >> Table columns do have special handling for array-valued bindings.  It's that they distribute the array values among their rows.  Table columns are not special in being able to bind through collection properties.  That's a feature of NSArrayController.
    >>
    >> The table column's bindings still have just an observed object (often the array controller) and a single key path (often of the form "arrangedObject.propertyName").  It's just that, when they receive an array from [observedObject valueForKeyPath:observedKeyPath], they know to pick one element from that array for each row.
    >
    > I have to speak under potential correction, since I don't have the implementations to refer to, but I don't believe this analysis.
    >
    > Typically, nothing can "bind through collection properties", because typically a binding involves (along with other things) an observation of the target object on a key path, and trying to set up such an observation will throw an exception.

    Go ahead and try it.  Instantiate an array controller and set up key-value observation on a key path which goes through its arrangedObjects property.  No exception.

    > If the "collection property" is not really a collection but a proxy (as you've described "arrangedObjects" to be), then the exception might be avoided, but the resulting observation will be useless because there's no KVO compliance. Specifically, I mean that if the key path is "arrangedObjects.propertyName", propertyName changes to individual objects won't propagate to an observer of the key path, at least not unless the arrangedObjects proxy itself observes every element of the underlying array, which is too horrendous to contemplate.

    It's not so horrendous.  It's what one is encouraged to do with one's own code if one needs to observe properties of the elements of a collection.  You observe the collection property itself to learn when elements are added or removed, and you observe the properties of the individual elements to learn when they change.  What do you think the -[NSArray addObserver:toObjectsAtIndexes:forKeyPath:options:context:] method is for?

    > In fact, it seems to me that logic dictates that table columns bound to something we normally write as "arrangedObjects.propertyName" do *not* observe on that key path. Again I don't know, but my assumption has always been that table columns observe the propertyName attributes of just the objects for rows that are currently visible. That in itself makes table columns "special", and their binding's so-called key path not really a key path.

    Well, the actual implementation is of course hidden.  However, there's nothing preventing it from using KVO on arrangedObjects.propertyName.  If it does otherwise, it is just an optimization.

    > Furthermore, it also seems to me that logic rules against any idea of the table column implementation *requiring* the construction of the *entire* array of row objects, because table views are virtual in the sense that they only reference the objects they need from moment to moment. Anything else would imply horrendous performance penalties for table views with thousands or millions of rows, but I don't believe there is any such penalty implicit in NSTableView itself.

    It is already necessary to "construct" the entire array of objects to know a) how many objects are in the table, and b) which objects belong to which rows in the face of sorting.  And, again, you're now talking about optimizations, not any requirement imposed by what can or can't be key-value observed.

    > Putting this another way, I find it hard to believe that table columns ever evaluate '[observedObject valueForKeyPath:observedKeyPath]', because that would seem to require construction of a potentially huge and costly array. I've always assumed that, assuming a so-called key path of the form "someArray.someProperty", table columns only ever evaluate '[[someArray objectAtIndex: rowIndex] valueForKey: someProperty]' for specific values of 'rowIndex', never '[[someArray valueForKey: someProperty] objectAtIndex: rowIndex]'.

    Again, you're guessing at optimizations.

    > Finally, we already know, from discussions on this list in the past, that (given the same key path) there *is* a semantic difference between table column bindings and other bindings to arrays, such as those supplying arrays of menu item titles to NSPopUpMenu. In that case, "arrangedObjects.propertyName" fails to be useful because it indeed behaves like a typical key path in a typical binding, rather than having the special binding behavior of table columns.

    The difference with pop-up menus is just whether or not the view understands a key path which returns an array.  I agreed in my earlier email that table columns are "smart" in that way.  For a key path which returns an array, they pick out individual elements for individual rows, which other views don't do.  Doesn't impact on the question of whether or not it's possible to bind or key-value observe through the arrangedObjects property of an array controller.

    You contend that table views are special.  What about this?  You can bind a text field's value to <arrangedObjects....>.  You can bind any view's enabled, editable, or hidden binding to the same sort of key path.  Was every one of those cases special-cased?  Can any of those bindings be implemented without actually observing the propertyName property of every object within the collection?

    Regards,
    Ken
previous month january 2012 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
Go to today