I've created a simple master detail application using two CoreData entities, two NSTableViews and two NSArrayControllers to connect everything through bindings and not write code. I want to remember and restore the selected detail row for every master row.
The master entity has a title and multiple details (one-to-many relation):
class Master: NSManagedObject {
}
// Generated
extension Master {
#NSManaged var title: String?
#NSManaged var details: NSSet?
}
The details entity just has a title:
class Detail: NSManagedObject {
}
// Generated
extension Master {
#NSManaged var title: String?
}
The first table view shows all master entities, the second table view shows all detail entities from the selected master entity in the first table. Both table views are configured to only allow single selection:
What I now would like to do is storing the index of the selected detail in the master entity. When the same master entity gets selected again, the second table view should automatically restore that selection and select the same detail again.
For the sake of ease I've added a simple selectionIndexes: NSIndexSet property to the Master entity. I've then bound this property to the details NSArrayController's selection indexes property:
Having this selection indexes binding actually stores the selected details index but some Cocoa code resets the selection to empty when the same master entity is selected again. This said the stored selection is overwritten with an empty selection.
Avoid Empty Selection and Preserve Selection are disable to avoid having NSArrayController selecting an item automatically if there's no item selected or always trying to select the same item again whenever its content changes (that's not what I want because different master entities might have details with the same title).
I've inspected the get and set calls to the selectionIndexes property such as:
class Master: NSManagedObject {
var _selection = NSMutableIndexSet()
var selectionIndexes: NSIndexSet {
get {
print("get", _selection)
return _selection
}
set(selection) {
_selection.removeAllIndexes()
_selection.addIndexes(selection)
print("set", _selection)
//print(NSThread.callStackSymbols())
}
}
}
And here's the call log:
Selecting the the 3rd detail of the first master entity:
set [number of indexes: 1 (in 1 ranges), indexes: (2)]
get [number of indexes: 1 (in 1 ranges), indexes: (2)]
Selecting another master entity:
get (no indexes)
And then selecting the first master entity again:
get [number of indexes: 1 (in 1 ranges), indexes: (2)]
set (no indexes)
get (no indexes)
As you can see it first restores index 2 (the 3rd detail entity), but then assigns (no indexes). Why is selectionIndexes re-assigned at this point? Here's the call stack of the last set call:
"0 SelectionTest 0x00000001000029fd _TFC13SelectionTest6Masters16selectionIndexesCSo10NSIndexSet + 797",
"1 SelectionTest 0x00000001000026ca _TToFC13SelectionTest6Masters16selectionIndexesCSo10NSIndexSet + 58",
"2 Foundation 0x00007fff9d2a6c13 _NSSetObjectValueAndNotify + 148",
"3 AppKit 0x00007fff9c9f09ae -[NSArrayController _setMultipleValue:forKeyPath:atIndex:] + 208",
"4 Foundation 0x00007fff9d25d80f -[NSObject(NSKeyValueCoding) setValue:forKeyPath:] + 287",
"5 AppKit 0x00007fff9c88a72e -[NSBinder _setValue:forKeyPath:ofObject:mode:validateImmediately:raisesForNotApplicableKeys:error:] + 411",
"6 AppKit 0x00007fff9c88a53c -[NSBinder setValue:forBinding:error:] + 248",
"7 AppKit 0x00007fff9ca958c3 -[NSControllerConfigurationBinder _updateSelectionIndexes:] + 226",
"8 AppKit 0x00007fff9ca95ab0 -[NSControllerConfigurationBinder _observeValueForKeyPath:ofObject:context:] + 111",
"9 Foundation 0x00007fff9d255a53 NSKeyValueNotifyObserver + 379",
"10 Foundation 0x00007fff9d25fd7a -[NSObject(NSKeyValueObservingPrivate) _notifyObserversForKeyPath:change:] + 1127",
"11 AppKit 0x00007fff9c6e57d8 -[NSController _notifyObserversForKeyPath:change:] + 206",
"12 AppKit 0x00007fff9c74242b -[NSArrayController didChangeValuesForArrangedKeys:objectKeys:indexKeys:] + 126",
"13 AppKit 0x00007fff9c74208f -[NSArrayController _selectObjectsAtIndexesNoCopy:avoidsEmptySelection:sendObserverNotifications:forceUpdate:] + 584",
"14 Foundation 0x00007fff9d23c178 -[NSObject(NSKeyValueCoding) setValue:forKey:] + 424",
"15 AppKit 0x00007fff9c88a72e -[NSBinder _setValue:forKeyPath:ofObject:mode:validateImmediately:raisesForNotApplicableKeys:error:] + 411",
"16 AppKit 0x00007fff9c88a53c -[NSBinder setValue:forBinding:error:] + 248",
"17 AppKit 0x00007fff9c868931 -[NSTableBinder tableView:didChangeToSelectedRowIndexes:] + 138",
"18 AppKit 0x00007fff9c868857 -[_NSBindingAdaptor tableView:didChangeToSelectedRowIndexes:] + 153",
"19 AppKit 0x00007fff9c728504 -[NSTableView _sendSelectionChangedNotificationForRows:columns:] + 119",
"20 AppKit 0x00007fff9c6f9723 -[NSTableView _enableSelectionPostingAndPost] + 424",
"21 AppKit 0x00007fff9c881a15 -[NSTableView mouseDown:] + 5358",
"22 AppKit 0x00007fff9cd83d1d -[NSWindow _handleMouseDownEvent:isDelayedEvent:] + 6322",
"23 AppKit 0x00007fff9cd84fad -[NSWindow _reallySendEvent:isDelayedEvent:] + 212",
"24 AppKit 0x00007fff9c6dd735 -[NSWindow sendEvent:] + 517",
"25 AppKit 0x00007fff9c6d9e49 -[NSApplication sendEvent:] + 2540",
"26 AppKit 0x00007fff9c60d03a -[NSApplication run] + 796",
"27 AppKit 0x00007fff9c58f520 NSApplicationMain + 1176",
"28 SelectionTest 0x0000000100004d07 main + 87",
"29 libdyld.dylib 0x00007fff892705ad start + 1",
"30 ??? 0x0000000000000003 0x0 + 3"
The obvious question now is: How can I make NSArrayController store and restore the selected details index into its master entity without having somebody else overwriting it?
Related
I have a table in Tabulator where all fields are Editable.
I would like to be able to switch the edit function on and off for different fields after the table has loaded.
I can hide the column: table.hideColumn("r1");
but it would be great if I could disable the editing.
As a bonus i'd also like to reformat the disabled column (change the background colour)
var table = new Tabulator("#table", {
height:"90%",
layout:"fitData",
ajaxURL:"data.php",
placeholder:"Data Loading...",
history:true,
cellEdited:function(cell){console.log("cell changed: (" + cell.getOldValue() + ") [" + cell.getValue() + "] - field: " + cell.getField() + " - id: " + cell.getRow().getIndex());},
columns:[
{title:"id", field:"id", sorter:"number", visible:false},
{title:"1", field:"r1", sorter:"number", editor:"input"},
{title:"2", field:"r2", sorter:"number", editor:"input"},
{title:"3", field:"r3", sorter:"number", editor:"input"},
{title:"4", field:"r4", sorter:"number", editor:"input"},
],
});
Many Thanks
AMEND - Added fiddle
In order to change bg cell color and also its font color use:
cell.getElement().style.color= "#ffffff"; //CHANGE CELL WHITE FONT
cell.getElement().style.backgroundColor = "#e68a00"; //CHANGE CELL BG COLOR
inside tabulator's title declaration..
for disabling editing try an object array with states: Let's say we have 3 columns and want only column 3 to be edited...
var isDisabled=["","","input"];
so you can use:
{title:"1", field:"r1", sorter:"number", editor:isDisabled[0].toString()},
{title:"2", field:"r2", sorter:"number", editor:isDisabled[1].toString()},
{title:"3", field:"r3", sorter:"number", editor:isDisabled[2].toString()}
Now you are ready to access these states directly in your array to change tabulator's edit status on the runtime!
Hope that helps!
[UPDATE1]
I've found this approach on Tabulator's Documentation:
var editCheck = function(cell){
//cell - the cell component for the editable cell
//get row data
var data = cell.getRow().getData();
return data.age > 18; // only allow the name cell to be edited if the age is over 18
}
//in your column definition for the column
{title:"Name", field:"name", editor:"input", editable:editCheck}
You can add a differed approach on every column and you can set corresponding criteria. i Also noted that once tabulator loads its data there is no way of disabling/enabling cell editing. But with the above method you define edit-ability before loading data!
[UPDATE2]
Im using cell.getColumn().getField(); to access columns name for triggering IF statement
var editCheck = function(cell){
var data = cell.getRow().getData();
if (incomingValue === 0){
console.log("Enable All");
return data;
}
if (incomingValue === 2 && cell.getColumn().getField()==="r1"){
console.log("Disabled col1");
return data.r1!==incomingValue; //FOR ENABLING EDIT TO OTHER COLUMNS EXCEPT THIS ONE
}
//... Here all IFs for the rest columns
};
I am inputting a random number from 1 to 25 into a text box on button press.
Every time i press the button, i want the text box to be emptied before the new number is stored - but putting self.outputbox.delete("1.0") into the button command only deletes the first character. i tried replacing 1.0 by something obvious like 2.0, but it never deleted both characters. At the moment, i am doing a very dirty solution by just calling self.outputbox.delete("1.0") twice - but i want to know how to delete the entire content of the text box.
this is the part:
def change_button_color(self):
randomcolor = self.get_random_color()
randombutton = self.random_button()
for z in range(0,1):
self.buttons['button{}'.format(randombutton)].config(bg=randomcolor)
for i in range(0,40):
self.outputbox.delete("1.0")
self.outputbox.insert("1.0","Button" + str(randombutton) + " " + "has the color" + " " + randomcolor)
as you can see, i currently have a rather dirty solution to clear the box
Do this
self.outputbox.delete("1.0", self.END)
it will clear all the text widget
And if you imported tkinter as tk do this
self.outputbox.delete("1.0", self.tk.END)
Here is the situation:
NSTableView bound to a NSArrayController
NSArrayController bound to an array of AnalysisResult objects
dynamic var analysisResults: [AnalysisResult] = [AnalysisResult]() // model for table view via resultsArrayController
The NSTableView has a column "result" bound to AnalysisResult.value
Now when the user clicks on a button, it triggers an analysis in a background thread, and modifies the objects in "analysisResults". It does NOT add nor remove any object from the array. It just changes the "value" property of the AnalysisResult objects.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
for (index, curAnalysisRes) in self.analysisResults.enumerate() {
curAnalysisRes.value = "some new value" // simplified but the value can be updated in many different places of the algorithm
}
dispatch_sync(dispatch_get_main_queue()) {
/* Ask the tableview to reload data in main UI thread */
self.myTableView.reloadData()
}
}
After the analysis, the UI thread is asked to update the table view.
BUT here is the problem: I get the following warning:
CoreAnimation: warning, deleted thread with uncommitted CATransaction; created by:
0 QuartzCore 0x00007fff8f06369a _ZN2CA11Transaction4pushEv + 318
1 QuartzCore 0x00007fff8f06319a _ZN2CA11Transaction15ensure_implicitEv + 276
2 QuartzCore 0x00007fff8f069719 _ZN2CA5Layer13thread_flags_EPNS_11TransactionE + 37
3 QuartzCore 0x00007fff8f069668 _ZN2CA5Layer4markEPNS_11TransactionEjj + 64
4 QuartzCore 0x00007fff8f06b12b _ZN2CA5Layer25set_needs_display_in_rectERK6CGRect + 333
5 QuartzCore 0x00007fff8f06afdc -[CALayer setNeedsDisplayInRect:] + 25
6 AppKit 0x00007fff872d12e8 _NSBackingLayerSetNeedsDisplayInRect + 319
7 AppKit 0x00007fff872d11a3 -[_NSBackingLayer setNeedsDisplayInRect:] + 61
8 QuartzCore 0x00007fff8f06af9d -[CALayer setNeedsDisplay] + 62
9 AppKit 0x00007fff872d198b -[NSView(NSInternal) _setLayerNeedsDisplayInViewRect:] + 606
10 AppKit 0x00007fff8729f88e NSViewSetNeedsDisplayInRect + 945
11 AppKit 0x00007fff8729f4d6 -[NSView setNeedsDisplayInRect:] + 48
12 AppKit 0x00007fff8729f2ed -[NSView setNeedsDisplay:] + 81
13 AppKit 0x00007fff872f2be1 -[NSTextFieldCell setObjectValue:] + 88
14 AppKit 0x00007fff873e3026 -[NSControl setObjectValue:] + 135
15 AppKit 0x00007fff873ce0c6 -[_NSPlaceholderTextFieldPlugin showValue:inObject:] + 110
The warning is caused by modifying the array bound to the arraycontroller.
I do not want to perform the analysis in the main thread because it freezes the UI.
How can I avoid the warning?
OK I have found the solution here, and it was pretty straightforward.
Since I was modifying the values of the array bound to the NSArrayController and since the controller is bound to a NSTableView (thus UI-related), I was obviously doing UI-stuff not in the main UI thread.
Technically speaking, since cocoa bindings use KVO (key-value observing), the change notifications triggered by my modifications in the array, were propagated in a non-UI thread, causing the up-mentionned message:
CoreAnimation: warning, deleted thread with uncommitted CATransaction;
Furthermore, an interesting side-effect, the piece of code:
self.myTableView.reloadData()
is not necessary anymore because the KVO notifications now propagate properly in the UI-thread.
I want the textview to add more line of code instead of changing/seting the text. The code below get called by a loop. The problem is only the last player's average score gets shown because the code changes the text per increment in loop and doesn't add the text below the previous text. I want each players average score to be displayed on the next line.
int average = playerTotalScore.get(player)/getRowSize();
textArea.setText(player + " average score: " + average);
Read out the current text value, append and set it:
String content = textArea.getText() + "\n" + player + " average score: " + average;
textArea.setText(content);
I have 1001 entries to be shown .. I have a dropdown box listing how many entries to be shown per page . (10,20,30,40,50). Initially i show 10 entries per page so the number of pages would be 101 .The text content initially shows page 1 of 101. Now when i change the number of entries to be shown per page to 20 , an javascript function is called and the max_page is set to 51 in tht function and text content is showing page 1 of 51 . Upto this its working fine . Now when i click on the last button , it shows the text content as page 101 of 101 ..instead of page 51 of 51 .. further clicks on it are showing wrong values .
$(document).ready(function()
{
$('.pagination').jqPagination({
link_string : '/?page={page_number}',
max_page :total_pages,
paged : paging
});
});
$("#items").change(function(){
$('.pagination').jqPagination({
max_page : total_pages
});
});
You need to update the max page (as follows) instead of re-instantiating the plugin.
$('.pagination').jqPagination('option', 'max_page', 51)