I have the following scenario in a NSoutlineView:
ParentObject [checkbox]
- ChildObject 1 [checkbox]
- ChildObject 2 [checkbox]
Each checkbox has a binding set up to a bool value of the respective object in a NSTreeController. When a user selects the parentObject checkbox, the respective children checkboxes should also be set. However, when a child object checkbox is set, the parent checkbox should not be affected. I cannot seem to get the parent functionality working properly.
My current attempted solution to the problem is:
when the checkbox is set call:
-(IBAction)CheckSelected:(NSButtonCell *)sender
{
// Somehow access the cells bound object in the NSTreeController ?????
}
However from my research I have not been able to find a way to get access to the cell's respective object in the NSTreeController.
Any insight on the problem would be greatly appreciated. I feel like this is a common problem that people would run into using an NStreeController and I am curious if I am taking the proper approach.
Thanks :)
The checkboxes shouldn’t be set up to call an action—they should be bound to a property, like, say, “isChecked”.
In your ParentObject you’ll have code similar to:
- (void)setIsChecked:(BOOL)isChecked;
{
_isChecked = isChecked;
for (ChildObject *childObject in self.children)
childObject.isChecked = isChecked;
}
Since the children’s checkboxes are also bound, the children’s new state will be reflected in the outline view immediately.
Related
I am working on chat system, i have a conversation page with a list view inside it, the list view delegates are text conversations. I want to add seen/unseen or read/unread to conversation delegates, the list view sourced by a custom model and i need something like delegates attached property which changes when the delegate is actually being viewed. is there any attached property which tell me the visibility status of each delegate items?
You have two problems to solve:
How to store the information that the message has been viewd
When to set it as read.
So one after another...
The custom property within the delegate is no good idea, as the information stored in it will be lost once the delegate will leave the viewport of the ListView and is destroyed.
The best solution would be, to store it in the ListView's model. This way it is also possible, if the model is somewhat persistend, that the information survives a restart of the app.
Maybe you might achive something with the next options I will present, in combination with Settings but I would consider this hackish.
If that is not possible, and the information does not need to survive an app's restart, you need to think about a way of storing it outside the ListView. For example you could have a array or object/dictionary to store it (beware: No change notifications). You might also use a second ListView that you keep in sync (which might prove not so easy!).
Finally you might put your ListView as a model in an Instantiator, which instantiates simple QtObjects with one property: read.
The latter would be the easiest way to store it outside the delegates and the model, so I will show you how it works:
Instantiator {
id: additionalInfo
model: customModel
delegate: QtObject {
property bool read // <--- This is a custom defined property
}
}
ListView {
model: customModel
delegate: MyDelegate {
onRead: additionalInfo.objectAt(index).read = true
Text {
text: additionalInfo.objectAt(index).read ? 'read' : 'unread'
}
}
}
And now for the second part: When is it acutally read
The problem with the visibility you already discovered. Items become visible even outside the listView when they are created for the buffer. So you can't use this information. But you have the following information:
The position of the listModel.contentItem (x and y)
The position of the delegate in relation to the listModel.contentItem
The width and height of your delegate
So you can say: The delegate is fully visible iff:
listModel.contentItem.x + delegate.x >= 0
listModel.contentItem.y + delegate.y >= 0
listModel.contentItem.y + delegate.y + delegate.height <= listModel.height
listModel.contentItem.x + delegate.x + delegate.width <= listModel.width
An example, on how a Item might check if it is visible in a Flickable (remember: a ListView IS a Flickable) can be found here
This is: If it is possible to have the whole delegate with the view. If it is larger - well you need to define some criterias when the delegate is considered read.
But again: If there is any posibility to have it as a role in your model, put it there!
There seem to be dozens of questions about binding a NSPopupButton, so I feel a little better about struggling so much with this, but none of them seem to fix my issue.
I have an NSManagedObject subclass that has a one to many relationship with another class. Let's say Foo has a relationship to Bar, so Foo has a property that is an NSSet of Bars.
I have created an NSArrayController and bound its contentSet to the Foo object and the 'bars' key path.
I then bind my NSPopupButton's content to the array controller's arrangedObjects and its contentValues to the array controller's arrangedObject (controller key) and 'name' (the property on Bar that I want to display).
This all works fine so far, but when I try to bind the NSPopupButton's selected object to the array controller's selection The NSPopupButton displays "<_NSArrayControllerObjectProxy". I also tried adding 'name' as the keyPath for this binding, and this does make the NSPopupButton display the name correctly, but then when I change the selection in the popup the app thrown an exception:
Unacceptable type of value for attribute: property = "name"; desired type = NSString; given type = Bar;
I guess this makes sense, as the popup is trying to set the string value of 'name' as the selected Bar. I would think I would therefore need to bind a selected object and a selected value, but the XIB will disable selected value if I have a selected object set.
I have also tried binding the selected value instead, and this half works (the array controller's selection does change) but the options in the popupmenu don't change to show the one that was deselected and hide the newly selected one).
Failing all of this I read an article here: http://blog.chrisblunt.com/cocoa-bindings-and-nspopupbutton/ that says NSPopupButton "NSPopUpButton does not record the user’s selection" and to instead store your selection somewhere other than the array controller. I tried putting a currentBar property in my window and binding the selection to that instead, and although I can see that currentBar is changing (because I have another view bound to it also) the label in the popup button does not change.
If anyone can help me out I'd be very appreciative.
The FooBar thing confuses me so here is my example based on real world objects.
ExpenseTransaction has attributes (date, trxDescription, category, amount).
Category has a single attribute (name)
ExpenseTransaction.category is a To-One relationship to Category (in other words a transaction can belong to only one Category).
Category.transactions is a To-Many relationship to ExpenseTransaction (in other words many transactions can belong to the same Category).
The UI for creating a new transaction or editing and existing one uses NSPopupButton to display the list of available Categories using the name attribute. For existing transactions the popup will display the selected transactions category.
Bindings for the Category popup are as follows:
Content (Category.arrangedObjects)
Content Objects (Category.arrangedObjects) - we want to link to the actual category not its name because the attribute is a relationship not a string value
Content Values (Category.arrangedObjects.name) - we want the name to be displayed in the popup list
Selected Object (ExpenseTransaction.selection.category)
Using your FooBar analogy:
Category has a relationship to ExpenseTransaction, so Category has a property (transactions) that is a NSSet of ExpenseTransactions. Now this is pretty much the inverse of my arrangement so I don't really know how or why you would populate the popup with ExpenseTransaction objects because only one selected item in the popup could be related to the Category object when in fact you need the whole set to be related. However the other way around works just fine because the popup would contain a list of all the Foo items and so whenever you select a Bar item the corresponding Foo item could be selected from the popup.
Hope this makes sense.
OK, so here's my situation :
I've got an NSDictionary, let's call it : myItem.
myItem.valueNames contains the values that I want to show up in the popup
myItem.values contains the values the popup must return (e.g. for valueName[0] -> value[0], and so on)
myItem.value contains the current value
How am I suppose to bind that? Even though I've studied the official reference, it still looks a bit obscure...
I'm currently binding :
myItem.valueNames to Content
myItem.values to Content Objects
myItem.value to Selected Value
and... all I've managed is that it shows the valueNames.
Any ideas?
Your model is a little strange to me. I wouldn't use an NSDictionary, I'd use a custom subclass with KVC/KVO compliant properties for each of these. Also, if the name of each value is a property of the value object itself, there's no need for a separate valueNames property. So, with that change, I'd do this:
Bind Content to modelObject with a key path of values
Bind Content Values to modelObject with a key path of values.name
Bind Selected Object to modelObject (or yourControllerObject if that makes more sense) with a key path of value (I'd name it selectedValue)
I have a ListView bound to an ObjectDataSource, I'm passing some custom parameters to the Insert and Update methods on my bound class methods by adding them to the event.Values map in the ListView ItemInserting/ItemUpdating events.
However when I try to do the same thing on the ItemDeletingEvent the additional parameters do not seem to be passed to the datasource ( If I register a listener for ObjectDataSource.ItemSourceDeleting I only see one parameter, effectively the 'id' of the row).
Is this an expected behavior? I can't see anything in the documentation that indicates as such.
I found a solution -
I Added a 'DeleteParameter' value with the same name as my desired 'custom' parameter to the ObjectDataSource declaration.
Then in the ItemDeleting Event get the ObjectDataSource.DeleteParameters["myparam"] and set the DefaultValue property. Seems like a hack, but it does work.
I'm developing a business application, using Silverlight for the UI and a WCF webservice for the back-end. In the database I have a number of lookup tables. When the WCF service returns a business object, one of the properties contains the entire row out of the lookup table instead of just the foreign key, so in the UI I can display things like the description from the lookup table without making another call to the service. What I am trying to do at the moment is provide a combobox bound to the entire list of lookup values and have it update properly. The business object I'm dealing with in this example is called Session and the lookup is called SessionType.
Below is the definition of the combobox. The DataContext is set to an instance of Session. I am setting an ItemTemplate because the combobox is displaying more than just a list of strings.
<ComboBox
x:Name="SessionTypesComboBox"
ItemTemplate="{StaticResource SessionTypeDataTemplate}"
ItemsSource="{Binding Source={StaticResource AllSessionTypes}}"
SelectedItem="{Binding Path=SessionType, Mode=TwoWay}"
/>
Both the business object and the lookup table are being loaded asynchronously via the web service. If I do nothing else, the combobox list will be populated with SessionTypes, but it will not show the initial SessionType value from Session. However Session will be updated with the correct SessionType if the combobox selection is changed.
What seems to be happening is that the SelectedItem binding cant match the SessionType in Session to its equivalent in the SessionType list. The object values are the same but the references are not.
The workaround I have found is to load the Session and the SessionTypes list, then update the current SessionType of Session with the corresponding one from the SesstionTypes list. If I do that then the combobox displays correctly. However to me this has a bad code smell. Because everything is loaded asyncronously, I have to determine when everything is available. Here's how I'm doing that:
In the code-behind of my Silverlight user control:
// incremented every time we get data back during initial form load.
private volatile int m_LoadSequence = 0;
...
// Loaded event, called when the form is er... loaded.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// load session types
var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
if (sessionTypes != null)
{
sessionTypes.DataLoadCompleted += (s, ea) =>
{
IncrementLoadSequence();
};
sessionTypes.LoadAsync();
}
// start loading another lookup table, same as above
// omitted for clarity
// set our DataContect to our business object (passed in when form was created)
this.LayoutRoot.DataContext = this.m_Session;
IncrementLoadSequence();
}
// This is the smelly part. This gets called by OnBlahCompleted events as web service calls return.
private void IncrementLoadSequence()
{
// check to see if we're expecting any more service calls to complete.
if (++m_LoadSequence < 3)
return;
// set lookup values on m_Session to the correct one in SessionType list.
// Get SessionType list from page resources
var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
// Find the matching SessionType based on ID
this.m_Session.SessionType = sessionTypes.Where((st) => { return st.SessionTypeID == this.m_Session.SessionType.SessionTypeID; }).First();
// (other lookup table omitted for clarity)
}
So basically I have a counter that gets incremented each time I get data back from the webservice. Since I'm expecting 3 things (core business object + 2 lookup tables), when that counter gets to 3 I match up the references.
To me, this seems very hacky. I would rather see the combobox specify a ValueMemberPath and SelectedValue to match the selected item with one in the list.
Can anyone see a cleaner way of doing this? This situation is very common in business apps, so I'm sure there must be a nice way of doing it.
Geoff,
To confirm I understand your problem: the databinding infrastructure doesn't seem to recognise that two objects you consider 'equal' are actually equal - therefore the initial SelectedItem isn't set correctly since the databinding doesn't find a reference-equals object in your StaticResource collection to match Session.SessionType.
You get around this by 'flattening' the references (ie. you force the Session.SessionType to be reference-equals in the Where((st)...First() code.
We have had a similar problem.
It does kinda of make sense that Silverlight won't automatically 'equate' two objects from difference 'sources' just because you know they represent the same data. Like you said "The object values are the same but the references are not". But how can you MAKE the databinding equate them?
Things we thought of/tried:
implementing .Equals() on the class (SessionType in your case)
implementing operator == on the class (SessionType in your case)
implementing IEquatable on the class (SessionType in your case)
making the collection only Strings and binding to a string property
but in the end we have given up and used the same approach as you - 'extracting' the correct reference-equals object from the 'collection' (after everything is loaded) and poking it into the SelectedItem-bound property.
I agree with you about the code-smell, and suspect there must be a better solution. So far all our debugging in the property accessors and no-op IValueConverters hasn't found a solution -- but if we do I'll post it back here too.
I'm not sure I'm fully understanding the problem (it's early :)) But can't you just transfer all the items you need in one call? (even if you have to wrap the 3 in a new DTO class), then you can just update the current session type using a complete event. It's still not perfect, but at least you don't have to keep any counters.
I'd also move all that logic to a ViewModel and just bind to that, but that's just me :)
You'd be better off binding to an ObservableCollection then using some other code (a View Model part of a MVVM isn't a bad choice) to update it in the background. That way you get separation from the UI and its a lot easier to handle the updates as the UI is just bound.
Thanks for the answers, all of the above were useful! I'm moving towards the MVVM way as well as combining several service calls into a single one (also reduces round-trip overhead). Looks like I'll stick with the lookup re-referencing for the time being - if I find a better way I'll post it as well.
geofftnz,
Have you find any nice solution for this?
CraigD,
I doubt that overriding Equals etc is a good solution. First, this is to be done inside the generated proxy class SessionType, so these changes will be lost on each service reference update. Second, notification in the SessionType setter (here SessionType is the same generated client proxy class) uses ReferenceEquals call... so that's one more place to touch the generated code! OK, the first thing can be done via handmade partial class SessionType (and so will not be lost after updates), but the second thing certainly cannot be done the same way.