I’ve just been testing my application in full screen mode and I’ve made a few interesting / odd discoveries. First when in full screen mode opening a new window results in a tab being opened in the full screen “window”. The toolbar appears at the top of the screen followed by a tab bar and then my window contents.
It makes sense and is really quite helpful, however, when I switch back to standard window mode the tabs remain. There is even control over them in as much as I can right click on a tab and send it to a new window, close it etc.
If I move it to a new window the content doesn’t get redrawn at the new correct height, leaving a trench at the bottom of the window. Dragging the window causes the content to snap back into place.
So going into and out of fullscreen mode can be detected by having a global property “wasFullscreen” and setting it in any Window.Resized event from Self.Fullscreen as follows:
sub Resized
If App.WasFullscreen And Not FullScreen Then
' Split all tabs
ElseIf Not App.WasFullscreen And FullScreen Then
' Join all windows
End If
App.WasFullscreen = FullScreen
end sub
However the issue is then how do I split the tabs into windows and conversely join all open document windows into a tabbed window.
I notice that there is a Cocoa “mergeAllWindows:” api and an “moveTabToNewWindow:” API, but if I have the Declares right I’m messing up some other way:
Declare Sub moveTabToNewWindow Lib "Cocoa" Selector "moveTabToNewWindow:" ( Any As Ptr )
Declare Sub mergeAllWindows Lib "Cocoa" Selector "mergeAllWindows:" ( Any As Ptr )
Calling either of these functions hard crashes the application. I’ve tried:
moveTabToNewWindow Self.Handle
and
mergeAllWindows( Self.Handle )
Both from within a Resized event and in Button Pressed events. Has anyone successfully managed this?
No need to invest in expensive plugins to solve this
//
// Disable auto tabbing in Sierra
#If TargetCocoa
Declare Function NSClassFromString Lib "Cocoa" (s As CFStringRef) As Ptr
Declare Function NSSelectorFromString Lib "Cocoa" (s As CFStringRef) As Ptr
Declare Sub setAllowsAutomaticWindowTabbing Lib "Cocoa" selector "setAllowsAutomaticWindowTabbing:" (cls As Ptr, ena As Boolean)
Declare Function respondsToSelector Lib "Cocoa" selector "respondsToSelector:" (p As Ptr, sel As Ptr) As Boolean
Dim nswCls As Ptr = NSClassFromString ("NSWindow")
If respondsToSelector (nswCls, NSSelectorFromString ("setAllowsAutomaticWindowTabbing:")) Then
setAllowsAutomaticWindowTabbing (nswCls, False)
End If
#EndIf
Thanks for that, however, it provides a really bad full screen experience. I would prefer to merge all windows into tabs when they enter full screen and move them back into windows when they switch out again.
I was kinda hoping that “mergeAllWindows” and “moveTabToNewWindow” would do that. I suspect I’m just not calling them correctly.
Full screen is a really bad experience anyway.
A simple little dialog with a handful of controls occupies the whole screen with acres of space.
Horrible.
Not for my App. Dialogs / Sheets don’t appear in full screen windows they appear as dialogs / sheets in the usual way. Only Document windows show in full screen at full size as far as I can see. At least for my usage.
Maybe it’s an API2 thing, or a Monterey thing. My app is now converted to API2. Fullscreen works pretty well as far as I can tell. New window becomes new tab, dialogs operate as they do in window mode etc. The only issue is that when I move into fullscreen mode the Windows do not collapse down to tabs and when I leave full screen they don’t revert to windows.
Alternatively, I could add functions to support tabs in windowed mode too. But then I would need to be able to call mergeAllWindows and other elements like it. As far as my code is concerned everything just remains the same.
There is a setting in System prefs to General to “Prefer tabs:” which can be set to always, which would allow you to test without any app changes at all.
Using MacOSLib NSWindow class, which I converted to API2 and the following code allows me to pop all windows into one.
Declare Sub mergeAllWindows Lib "Cocoa" Selector "mergeAllWindows:" ( Any As Ptr )
Var oNSWindow As New NSWindow( Self )
mergeAllWindows oNSWindow
The following code will split the tabs into separate windows when you return from fullscreen (or any other time you want):
Declare Sub moveTabToNewWindow Lib "Cocoa" Selector "moveTabToNewWindow:" ( Any As Ptr )
For iWindow As Integer = 0 To App.WindowCount - 1
If App.Window( iWindow ) IsA Window1 Then
Var oNSWindow As New NSWindow( App.Window( iWindow ) )
moveTabToNewWindow oNSWindow
End If
Next
The “IsA Window1” part is to identify the class of the document windows you have. It could equally be any other code you wish to distinguish your documents from any other form of window, that likely wouldn’t be in a tab.
It doesn’t seem to work in the resize event. Likely because it is called from a window being resized.
Equally the merge code could be used to implement the Window menu “Merge All Windows” item you see in Finder.
AFAIK, this is NOT standard behavior, so I would recommend having an option to either enable this or disable it in the preferences for those that find it too jarring.
So in the end I decided to implement tabs are they are in Finder.
I added extension methods to MacOSLib NSWindow class as follows:
Public Sub AddTabbedWindowOrdered(tabbedWindow as NSWindow, orderingMode as NSWindowOrderingMode = NSWindowOrderingMode.NSWindowAbove)
#If TargetCocoa
Declare Sub addTabbedWindow Lib CocoaLib Selector "addTabbedWindow:ordered:" _
(obj_id As Ptr, tabbedWindow As Ptr, orderingMode As NSWindowOrderingMode)
Dim tabbedWindowRef As Ptr
If tabbedWindow <> Nil Then
tabbedWindowRef = tabbedWindow
End If
addTabbedWindow Self, tabbedWindowRef, orderingMode
#Else
#Pragma unused tabbedWindow
#Pragma unused orderingMode
#EndIf
End Sub
Public Sub MoveTabToNewWindow()
Declare Sub moveTabToNewWindow Lib "Cocoa" Selector "moveTabToNewWindow:" ( Any As Ptr, Sender As Ptr )
moveTabToNewWindow Self, Nil
End Sub
Public Sub SelectNextTab()
Declare Sub selectNextTab Lib "Cocoa" Selector "selectNextTab:" ( Any As Ptr, Sender As Ptr )
selectNextTab Self, Nil
End Sub
Public Sub SelectPreviousTab()
Declare Sub SelectPreviousTab Lib "Cocoa" Selector "selectPreviousTab:" ( Any As Ptr, Sender As Ptr )
SelectPreviousTab Self, Nil
End Sub
Public Function TabbedWindows() As NSWindow()
#If TargetCocoa
Declare Function tabbedWindows Lib CocoaLib Selector "tabbedWindows" ( obj_id As Ptr ) As Ptr
Dim retArray() As NSWindow
Dim arrayRef As Ptr = tabbedWindows( Self )
If arrayRef <> Nil Then
Dim ns_array As New NSArray(arrayRef)
Dim arrayRange As Cocoa.NSRange = Cocoa.NSMakeRange( 0, ns_array.Count )
Dim m As MemoryBlock = ns_array.ValuesArray( arrayRange )
Dim n As Integer = arrayRange.length - 1
For i As Integer = 0 To n
retArray.append New NSWindow( Ptr( m.UInt64Value( i * SizeOfPointer ) ) )
Next
End If
Return retArray
#EndIf
End Function
Public Sub MergeAllWindows()
Declare Sub mergeAllWindows Lib "Cocoa" Selector "mergeAllWindows:" ( Window As Ptr, any As Ptr )
mergeAllWindows Self, Nil
End Sub
Usage was as follows:
Function FileNewTab() As Boolean
' Menu handler for New Tab
Var oOwner As NSWindow = Self
Var oCSW As New CSW
Var oTab As NSWindow = oCSW
oOwner.AddTabbedWindowOrdered oTab
End Function
Function WindowMergeAllWindows() As Boolean
' Menu handler for Merge All Windows
Var oCSW As CSW
Var oNSWindow As NSWindow = Self
oNSWindow.MergeAllWindows
Return True
End Function
Function WindowMoveTabToNewWindow() As Boolean
' Menu handler for Move Tab to New window
Var oNSWindow As NSWindow = Self
oNSWindow.MoveTabToNewWindow
Return True
End Function
Function WindowShowNextTab() As Boolean
' Menu handler for Show Next Tab
Var oNSWindow As NSWIndow = Self
oNSWindow.SelectNextTab
Return True
End Function
Function WindowShowPreviousTab() As Boolean
' Menu handler for Show Previous Tab
Var oNSWindow As NSWIndow = Self
oNSWindow.SelectPreviousTab
Return True
End Function
Enabling the right menus at the right time, in MenuBarSelected (API2) or EnableMenuItems (API1).
Set the tab menus to have AutoEnable = False
Sub MenuBarSelected() Handles MenuBarSelected
' Tabs only work in 10.12 and above
If System.Version > "10.11" Then
FileNewTab.Enabled = True
' This is a structure in my app that keeps track of how many open documents there are.
' You will need to adjust accordingly.
If App.OpenWindows.NumWindows > 1 Then
WindowMergeAllWindows.Enabled = True
End If
Var oNSWindow As NSWindow = Self
Var TabbedWindows() As NSWindow = oNSWindow.TabbedWindows
' If there are tabs on the window
If TabbedWindows.LastIndex > -1 Then
WindowMoveTabToNewWindow.Enabled = True
WindowShowPreviousTab.Enabled = True
WindowShowNextTab.Enabled = True
End If
End If
End Sub
There’s only one more issue. When you move a window out of Tab mode into normal Window mode there seems to be a gap developed at the bottom of the window. If you have controls locked to there you may have to adjust their sizes or locations. Also windows entering Tabbed get shorter so you have to play with that.
Hope this helps someone.
PS I would add these additions into MacOSLib however, I only have a Light license and so can only save in binary. If someone would like to add them I’m more than happy to assist.