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.
Related
I am perplexed about the SizeToContent method of the CMFCMenuButton control.
This is my dialog in the IDE:
As you can see, I have specifically made the button wider than the two on the far right.
I added the following code to OnInitDialog:
// Resize (if required)
const auto sizNewButton = m_btnReset.SizeToContent(true);
CRect rctButton;
m_btnReset.GetWindowRect(&rctButton);
if(sizNewButton.cx > rctButton.Width())
{
m_btnReset.SizeToContent();
}
Yet, when I run my application in English:
It has made it smaller. My application supports 50+ languages by using satellite DLLs and I was hoping to only resize to content if it was required. But it seems to resize it anyway. Have I missed a step here?
I have checked the properties for the control in the IDE and it is not set to auto resize:
I notice that the help documentation states:
The new size of the button is calculated to fit the button text, image, and arrow. The framework also adds in predefined margins of 10 pixels for the horizontal edge and 5 pixels for the vertical edge.
I had a look at my button:
Default size: 48 x 23 (the GeWindowRect result).
Calculated size: 57 x 23 (the SizeToContent result).
If I adjusted my code like this:
if((sizNewButton.cx - 10) > rctButton.Width())
That would bring it down to 47 and thus would not resize. I am assuming the code is not working right because of the padded margin that GetWindowRect knows nothing about.
Searched it, and found that the problem is MFC's CMFCMenuButton::SizeToContent() implementation in afxmenubutton.cpp:
CSize CMFCMenuButton::SizeToContent(BOOL bCalcOnly)
{
CSize size = CMFCButton::SizeToContent(FALSE); // <==== The culprit!!!
size.cx += CMenuImages::Size().cx;
if (!bCalcOnly)
{
SetWindowPos(NULL, -1, -1, size.cx, size.cy, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER);
}
return size;
}
That is, it calls the base implementation of SizeToContent() with the bCalcOnly parameter set to FALSE, which means it will also resize the control to just fit the text (without the drop-down arrow). This is less than required for the text plus the arrow, and of course the original size is lost.
A workaround can be get the (original) width, before the SizeToContent() call, and work with this instead of the new one:
CRect rctButton;
m_btnReset.GetWindowRect(&rctButton);
const auto nOrigWidth = rctButton.Width(); // Store the original width
const auto sizNewButton = m_btnReset.SizeToContent(true); // This resizes the control!!!
if (sizNewButton.cx > nOrigWidth) // Compare to the original width rather than the new one
m_btnReset.SizeToContent();
else // Restore original width
m_btnReset.SetWindowPos(NULL, -1, -1, nOrigWidth, sizNewButton.cy, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER);
Alternative Workaround:
Define a new CMFCMenuButton-based class, overriding SizeToContent() - in the implementation call the base CMFCButton::SizeToContent() with the bCalcOnly parameter passed by the caller, not with FALSE. Map the control to this class instead of CMFCMenuButton. That is use a class that fixes it. Too much of an overkill for just a workaround though.
I am working on a slide show where I insert N number of rows. There are two issues...
I don't know the number of rows, there is a max of 50 we will allow but even 50 will go out of the slide.
The text I will add to each column can also be somewhat long.
Right now, my current approach is allowing 15 rows, creating a new slide to add the next 15, and so on until I hit 50.
What I would prefer to do is get the size of the table and after I finish one row, I would like to check if it is overflowing out of the slide, and if it is, I'll remove it, make a new slide, and add it to the new table.
An alternative approach if possible, is keep the row height locked, and allow any extra text to kinda be hidden until the cell is selected (similar to an excel spreadsheet).
Using a similar approach here
Apache POI get Font Metrics
Solution:
stringList is repopulated for each row
int max = 0;
int j = 0;
for (String text : stringList) {
AttributedString attributedString = new AttributedString(text);
attributedString.addAttribute(
TextAttribute.FAMILY, "Avenir Book", 0, text.length());
attributedString.addAttribute(TextAttribute.SIZE, (float)14);
TextLayout layout = new TextLayout(attributedString.getIterator(), fontRenderContext);
Rectangle2D bounds = layout.getBounds();
max = Math.max(max, (int) Math.ceil((bounds.getWidth() * covertToEmu)
/ (table.getTblGrid().getGridColList().get(j).getW())));
j++
}
covertToEmu is just a number...bounds.getWidth() is in 72 dpi and table.getTblGrid().getGridColList().get(j).getW() (the width) is in EMU. 72 dpi is just the pixels in inches...which is 72 pixels per inch. An EMU per inch is 914400.
So convertToEmu is 914400 / 72 = 12700.
The max is the number of "rows" it takes...the rest is kinda hard coded, but I split the list of data I have into sublists and add it to each slide. I know 20 rows is a good fit so if it gets higher than that I create a new list, to add to a new slide.
Also worth noting I am using CTTable, which you can get from a method in XSLFTable.
I have two list controls and both are vertically scroll-able separately.
However I want to synchronize scroll also I would like to hide the vertical scroll bar in list control-1.
On the other hand if you scroll vertically list control-2, then the list control-1 should scroll down automatically the same amount of items in such way that the options on the both the list boxes should always appear in the same row.
How can I achieve this in MFC?
I do this with a connection between two list views through the document, but the end result is a command to the Scroll member of the slave list control.
So handle the ON_WM_VSCROLL() in the master, I actually have a custom notify but you may want to just shortcut to from the likes of in the master:
if( pS->nSBCode == SB_THUMBTRACK )
GetDocument( )->SetSplitScrollPos( pS->nPos );
How ever you work past to the likes of 'SetSplitScrollPos' it ends up with this at the slave:
void CLCtrl::ScrollToVPosition( long inPos )
{
long scroll= ( inPos - curVScrollPos );
Scroll( scroll << 20 );
curVScrollPos= inPos;
}
The 'Scroll' call is a CListCtrl member, so you could:
mySlaveCtrl.Scroll( ... );
Now, I'm sorry, but I don't recall why the shift of 20 as '<< 16' should move the value to the hi_word, but it needed to be 16 times greater, (20 - 16). I did not write in the required comments.
To wit, it may be as simple for you to handle the master ON_WM_VSCROLL and:
if( pS->nSBCode == SB_THUMBTRACK )
mySlaveCtrl.Scroll( ( ps->pos - curVScrollPos ) << 20 );
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?
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)