Re: Binding Buttons and Popup Menus to NSDictionary made from a prefs plist?
Re: Binding Buttons and Popup Menus to NSDictionary made from a prefs plist?
- Subject: Re: Binding Buttons and Popup Menus to NSDictionary made from a prefs plist?
- From: Ken Thomases <email@hidden>
- Date: Wed, 28 Jan 2009 02:52:51 -0600
On Jan 26, 2009, at 9:09 AM, Robert Monaghan wrote:
I am trying to wrap my head around using a plist.
When you refer to a "plist", are you referring to a property list data
structure as it might exist in memory, or a property list file? Or
perhaps you're referring to user defaults?
In memory, a property list is just an abstraction. It's an NSString,
NSNumber, NSData, NSDate, or a collection (NSArray or NSDictionary)
containing only theses types. (For simplicity, I'm eliding mutable
versions of these types and their Core Foundation equivalents.)
Everything that I have seen on the mailing list so far, involves
using an NSTableView to work with NSDictionaries.
I'm sure that more reading will turn up lots of other examples that
don't use NSDictionaries.
Key-Value Coding, Key-Value Observing, and Bindings all deal with
objects which have properties, which may be referred to by name,
a.k.a. key.
NSMutableDictionary is useful as a generic object which may have any
set of properties. NSMutableDictionary can be nice for some rapid
prototyping. However, you'll probably find that it will be
insufficient for a real application.
The problem with using NSMutableDictionary for your model objects is
that it's pure data with precious little behavior. Object-oriented
programming is about combining data with the operations (behavior)
that act on it. Unless your model has no smarts, no "business/domain
logic", then a dictionary is not going to be sufficient.
So, you should definitely consider replacing your uses of
NSMutableDictionary with real custom classes that represent your model
objects. Sometimes doing so forces thinking about your design that
exposes problems with it.
Here is what I have set up.
I have several NSButtons and pre-populated NSPopup Buttons, where
the index values or states are stored as NSNumbers inside a plist
file. Fairly easy to figure out. You just specify a key, and you get
an NSNumber Object.
Here you say "plist file", but you can't create bindings to a file.
So, what's really going on?
I am now trying use several of these plists. I have a list of
projects in an NSTextColum, each with its own plist. I want to
select one of the projects in the tableview, and have the controls
changed to reflect the states from the appropriate plist. Again, in
theory, plists should work well, here.
NSTextColum? I'm going to assume you mean a table column with text
field cell. From what you're describing -- selecting a row in a table
updates controls to reflect the selection -- this sounds like a master-
detail interface.
Yes, "plists" should work well here, in the sense that you can model
the set of projects using a to-many relationship represented with an
array, and each element being a dictionary describing a project.
By the way, you haven't described the object having this to-many
relationship.
So far, I have an NSArrayController working well, with a list of
projects/plists. However, I can't seem to get the bindings to work
for the various controls. I am attempting to bind the buttons and
popup menus to *something" that that can drive them. I not only want
the controls to be updated with each project that is selected, but
have the NSDictionary that is holding the plist, to be updated when
the user changes the controls.
You use the verb "drive" here (and below), but I'm not sure I follow
what you mean. You want something to "drive" your buttons and pop-up
menus? In what sense? Do what to them?
In the Model-View-Controller (MVC) design pattern, if anything is
going to act on the views, it would be the controller. Often though,
the views "drive" the controller, in the sense of sending action
messages to it or setting its properties through bindings. The views
also get their values from the controller through bindings, built on
KVO.
Here is what I have tried:
NSDictionaryController.
I have an observeValueForKeyPath set up, to watch my
NSArrayController, with a list of projects.
Once I have a project selected, it then binds an NSMutableDictionary
to the NSDictionaryController
Something like this: [seqPrefsCtlr bind:NSContentDictionaryBinding
toObject:self withKeyPath:@"prefsDict" options:nil];
Honestly, that is clear as mud to me.
Why are you using an NSDictionaryController? If you're using an
NSMutableDictionary as a model object, that doesn't necessarily mean
that an NSDictionaryController is appropriate. It's only appropriate
if you need to treat the set of key-value pairs as an array, where the
keys are not properties but part of the data. If you're using a
dictionary as an entity, where its keys are properties, then I don't
see where NSDictionaryController enters into it.
An NSArrayController is a mediating controller. There should be a
coordinating controller somewhere, which you haven't described. The
NSArrayController doesn't "own" the list of projects, it just presents
a bindings-compatible interface to the list and handles some common
tasks (like maintaining a selection).
For what purpose are you directly observing the array controller?
What object is doing that observing and what is it doing in response
to the change notifications it receives?
Why are you doing the binding in code? It's not wrong, but it seems
like you're doing things the hard way, possibly because you don't
understand the easy way.
No problem there.. But then I can't seem to figure out what to bind
the assorted NSButtons and NSPopup menus to.
A typical case of a master-detail interface would be to have a
controller exposing a to-many relationship property. Let's call it
"projects".
There would be an array controller with its content array bound to the
controller, with the controller key being "projects".
There would be a table view with a column bound to the array
controller, controller key "arrangedObjects", and model key "name".
This supposes that each project object has a property "name".
Next to the table there might be various detail controls. Each would
be bound to the array controller, with controller key "selection" so
that they show stuff related to whatever is selected in the table and
update themselves as the selection changes. For each control, the
model key would be different, depending on what aspect of the selected
project that control represents. There might be a checkbox with its
value binding bound to model key "completed" that shows whether a
project has been completed, and allows you to toggle that state.
Obviously, this supposes that a project has a "completed" property.
Pop-up menus are more complicated. For this illustration, let's
suppose that a project may have a manager and we want the user to be
able to select the manager for the project from a pop-up.
First is the question of what choices should the pop-up show. It may
be that there's one set of managers that may be assigned to all
projects -- the available managers doesn't change as you select
different projects. In that case, your coordinating controller should
expose an array property with those choices, say "managers". There
would be a second array controller bound to that controller property.
The pop-up's content would be bound to the arrangedObjects property of
that. It may be useful to separately bind the pop-up's contentValues
and/or contentObjects bindings, too, if the managers are represented
by more than just a name.
Alternatively, let's suppose that each project has a different set of
possible managers. That might be represented as an
"availableManagers" property on the project object. So, there would
be an array controller bound to the first array controller, using a
controller key of "selection", and model key property
"availableManagers". That is, this second array controller mediates
access to the list of available managers of the project which is
currently selected in the first array controller. Again, the pop-up's
content would be bound to the arrangedObjects of this second array
controller.
The second question is how the pop-up's current selection is related
to the project object. Let's assume that projects have a property
called "manager". Then the selectedObject or selectedValue binding of
the pop-up would be bound to the first array controller, controller
key "selection", model key "manager". That is, going backward, you
want the pop-up to reflect and affect the "manager" property of a
project. Which project? The one which is selected in the table,
which is tracked by the "selection" property of the first array
controller.
An important note: in the above, I wrote about keys naming properties
of objects. It doesn't matter what kind of objects, just so long as
they properly implement properties, including KVC and KVO compliance.
They might be instances of custom classes, or they might be
dictionaries. It doesn't much matter.
My other idea was to subclass NSArrayController, and override
selectedObjects.
With this, I wanted to bind each control to the NSMutableDictionary
in File's owner (somehow), and have each control observe a key in
the dictionary.
Again, I'm not sure what you're describing or why you think you need
to go this way. It definitely sounds like the hard way. Subclassing
NSArrayController should not be necessary for what you want to do.
This seems to be a one way street, however. I can get the keys in
the dictionary to initially set the buttons/popups, but nothing else.
This goes back to the question of how you're establishing your "plist"
data structure in memory. Are you sure you're getting mutable
containers? If you're using NSPropertyListSerialization, be sure to
pass the NSPropertyListMutableContainers mutability option.
Is there any sort of example that shows how an NSDictionary can
drive a pile of controls with bindings? Or should I just brute-force
this, and write other code?
Again, the verb "drive" sounds wrong to my ear, especially since
NSDictionary is a passive data collection and drive is an active
verb. It also seems to get the roles of things the wrong way around.
The model doesn't "drive" the view. The model should be unaware of
the view and even the controller.
Also, as I said above, the use of NSDictionary shouldn't change how
you think about this problem, nor does it change the way bindings
work. Any bindings example applies, just so long as you think in
terms of objects having properties, which you can reference by name
a.k.a. key.
So, Apple's discussion of the Master-Detail Interface here applies,
regardless of how the model is implemented:
http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaBindings/Tasks/masterdetail.html
Cheers,
Ken
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden