I have some hierarchical data model that I'd like to present in an NSOutlineView. I'm binding a tree controller to the outline view to provide data and to handle selection and binding to other views.
However, I only want to show only part of the data in my model to the outline view. (Each object in my hierarchy has an array of child objects, but I'd only like some of these child objects to appear as child nodes of the node in the tree.) I wish I could just attach a filter predicate to the tree controller, but it seems that NSOutlineView doesn't support filter predicates.
I think that this design requires an NSOutlineViewDataSource to filter my data model, and an NSTreeController bound to the data source and the outline view. However, none of the binding outlets in the tree controller (Content Array, Content Object, Content Set, etc.) seems appropriate for binding a data source.
Any ideas? Thanks in advance...
You can try feeding the data to your array of child objects through an array controller.
Here's how I'd do it. Override the accessor method in your represented object and return a filtered array from your array controller.
In general, NS[Outline|Table]ViewDataSource and Cocoa Bindings is an "either/or" choice. Mixing the approaches, while perhaps not strictly impossible, will likely lead to unpredictable results.
You mention binding a filter predicate to the Outline View itself and not to specific nodes, so I surmise that one filter predicate for all nodes might be "good enough." If that's the case, then one solution to this would be to expose a second children-vending property on your model, maybe filteredChildren, and tell the NSOutlineView to use that to access children instead of the your default/complete children-vending property. If you need functionality like drag reordering, this approach might prove to be non-trivial, but it should be easy to explore this approach regardless.
If you need a different filter for each node, or if the filter changes dynamically, this task would likely have crossed over into being a case that was a good candidate for implementing NSOutlineViewDataSource (and a poor candidate for using Cocoa Bindings.)
Related
I'm trying to create a Mac OS Core Data application that has an array of parent objects (called Levels) each of which contains a collection of child objects (called Blocks) via a one-to-many relationship. I have a table view successfully controlling the array of levels, and a custom view object that draws the blocks graphically based on positions held in x and y properties of my Block model class. I can add blocks to the currently selected level, remove them, select and move them around in the custom view, and have bound text fields to various other properties of the Block class which I can use to edit those values. All of this information is successfully saved and restored to and from the core data repository with no issues output to the debugger. Wonderful. I've used an NSArrayController for the Levels and another for the Blocks that is bound to the current selection of the Levels array controller, in what I've read is a pretty standard way.
Now, my Block class is actually an abstract class, and what I actually instantiate are various child classes of Block (eg RedBlock, GreenBlock, BlueBlock classes). Each sub-class has a separate set of properties that only apply to that type of block (so RedBlock has a "text" property that none of the others have, BlueBlock has an integer "value" property, etc). I want to create an inspector that will change depending on the type of the Blocks that are currently selected in my custom view. To try this, before I start creating subviews for each type of Bock, I have created a text field that I want to bind to the currently selected RedBlock's "text" property, preferably showing nothing when Blocks of other kinds are selected. This is where I'm stuck. I've added another NSArrayController in Entity mode with RedBlock specified as it's type so I can bind to the "text" property, and tried adding a filter predicate based on the class type. I've also tried various other configurations and bindings, but I'm either getting exceptions, or corrupt values in the text field that I bind to that controller, or other weird bugs and general brokenness.
I've googled around for an example of an inspector that can cope with a heterogenous array of objects (as that's essentially what I'm trying to do) but so far no luck.
So, my question is - am I going about this the right way? Should I be trying to create an NSArrayController that filters the selected items in my Blocks array controller somehow? If so, should that be straightforward or is there some trick that I've missed? If not, what is the best way to do this?
This approach should work, provided you limit the inspector to displaying view which bind to properties which apply to the entire selection. You don't need a second array controller.
To test the simple example, try creating a data set with only blocks, see that your bound control loads without raising exceptions, and that it updates the object correctly.
Once that is working, create separate views for each type, and display and hide them when the selection will change. Again, if you have a heterogenous selection, hide them all.
I have a conditional business logic that determines whether a property from a model should be displayed in a view. according to best practices where would be the place to implement it?
Implementing this logic in the view level does not seem right to me.
Thanks
IMO, it is belonged to the Model. I would put that business logic in IsRequiredYourProperyName property in the model.
Really? I would have thought that would be fine in the view provided you're passing the boolean indicating whether or not it should be displayed as part of the ViewModel. The view shouldn't be querying an outside resource to see if it should render certain UI elements, but if everything it needs to determine what to render is in the ViewModel, what's wrong with a simple if{} statement? Alternatively if a conditional display property is common you could create a custom DisplayTemplate or EditorTemplate and for it and implement the logic there.
So your ViewModel should wrap everything you want to send to the view. in your case it sounds like it should wrap your DomainModel and some sort of dictionary or KeyValuePair collection detailing if each property should be displayed or not as booleans. That's what I would do anyway.
Result should be an settings panel with an OutlineView and "add item", "add group" and "delete" buttons. The buttons add entries to an NSOutlineView. The data is stored in a NSMutableDictionary (or whatever is suitable). Sorting/DragDrop enabled for the OutlineView.
What is the best or most comfortable way for this (and write less code)?
Modifying NSMutableDictionary, NSOutlineView refreshes from NSMutableDictionary?
Modifying NSOutlineView, Result is stored in NSMutableDictionary?
... NSTreeController?
... CoreData?
What's best practice for that?
Thanks in advance!
This is a pretty broad question. You should always store your model data in a model object of some kind, be that a Core Data entity, an NSMutableDictionary or a custom object of your own creation. You should definitely NOT be storing the data in an NSTreeController or NSOutlineView instance, these are not model objects.
If you're using Core Data for the rest of your app and you need to persist the data that is manipulated by the outline view then this is a good choice, but it might be overkill if you have only simple requirements.
To control what is displayed in the outline view you can either use NSTreeController or your own controller object that responds to the NSOutlineView datasource and delegate protocols. In practice you might use both, as some things such as whether or not an item is a group item can only be controlled by the NSOutlineView delegate methods.
In my personal experience I've found that NSTreeController can be very difficult to deal with for anything beyond very simple tasks and I now longer use it, I find it's much simpler to just use the datasource methods in my own controller.
As far as modifying the contents of the outline view, you should always modify the model via a controller, you should never update the view directly. You would implement methods such as -add: in your controller or use the -add: method of NSTreeController if you're using it.
Your view's controller should then detect the change in the model and ask the view to update. The view controller and model controller can be the same object but they don't have to be. Key-Value Observing is a useful technology that can inform your controller of a change in the model.
Here's some sample code from Apple that you might find useful:
http://developer.apple.com/mac/library/samplecode/SourceView/
http://developer.apple.com/Mac/library/samplecode/AbstractTree/
I've got an NSOutlineView acting as a source list for my application, and my data model is done with Core Data. I'd like to use bindings (if possible) to glue these things together as follows:
I have three main entities in my model, for sake of example let's call them "Stores", "Cars" and "People".
My goal is to have the outline view have 3 "groups" (expandable nodes, like PLAYLISTS in iTunes), each group representing and listing one of my entities, and also I've got three buttons at the bottom of my window to "Add Store", "Add Car", etc which I'd like to have wired up to perform that action.
So far in my window's nib I've got a TreeController which is bound to my NSManagedObjectContext instance of my window controller, but I can't figure out how to properly bind and populate the outline view from the TreeController.
Is this possible with bindings? I've seen one tutorial where a second managed object model is created, with entities for the outline nodes, but some comments on the article said this was a bad idea. I'm not really sure how to proceed, any help would be wonderful!
Try the Cocoa Bindings Programming topics: Providing Controller Content section
What I did was create custom classes for my entities, and added isLeaf properties to them. For the top level (Stores, Cars, People) I return no. For leaf nodes (a car, a person, etc) I return YES.
The top level needs to have a to-many relationship to the leaf nodes, I called this children.
In Interface Builder, I set the NSTreeController's mode to Entity, name: Groups. It's bound to the managedObjectContext. In the Key Paths I set the Children attribute to children, and Leaf to isLeaf.
I have a tree data structure which I would like to put into an NSBrowser. I have found complicated methods that involve App Delegates, but I would just like to insert the rows as I come across them.
An NSBrowser is a view. A view generally doesn't hold onto a whole complex tree of model objects—that's a controller's job. That's why you appoint your custom controller (and owner of your model objects) as the delegate of the browser: You own your entire model, and the view gets parts of it from you as it needs them.