NSWindowDelegate willPositionSheet

I need to implement a method on a window’s delegate. This one specifically, but for this question the specific method doesn’t really matter: https://developer.apple.com/documentation/appkit/nswindowdelegate/1419611-window?language=objc

I found some old code that gets me close https://forum.xojo.com/17545-disable-resizing-window-from-top-nswindowdelegate/p1#p157628 but it causes new problems, such as window contents not resizing. Likely because I’m replacing the delegate object which the framework is setting.

Without using plugins, does anybody have any ideas that might help?

Here’s a solution, though it’s an unsupported hack that may break in a future Xojo version. Another user gave me some clues privately to avoid the “should you / shouldn’t you” conversation. But since this goes into an open source project anyway, I figure I might as well share what I did.

In the window’s constructor (in this case MainWindow) the goal is to modify the XOJWindowController class to add a method reference to your own shared method. This only needs to happen once. So we’ll use a shared property to track wether or not this has happened.

Herein lies the risk. If Xojo changes the delegate class name or implements the method themselves, this code breaks. The good news is that Xojo has little reason to rename the class, because we could just find it again. And implementing the method themselves would be good for us too, as this would be easier to implement.

Onto some usable code:

Shared Property DelegateClass As Pointer

Function Constructor ()
  #if TargetCocoa
    Declare Function NSSelectorFromString Lib "Cocoa" (SelectorName As CFStringRef) As Ptr
    Declare Function NSClassFromString Lib "Cocoa" (ClassName As CFStringRef) As Ptr
    Declare Function class_addMethod Lib CocoaLib (Ref As Ptr, Name As Ptr, Imp As Ptr, Types As CString) As Boolean
    
    If Self.DelegateClass = Nil Then
      Self.DelegateClass = NSClassFromString("XOJWindowController")
      If Not class_addMethod(Self.DelegateClass, NSSelectorFromString("window:willPositionSheet:usingRect:"), AddressOf Handler_WillPositionSheet, "{NSRect=ffff}@:@@{NSRect=ffff}") Then
        Break
        Return
      End If
    End If
    
    Super.Constructor()
  #endif
End Function

Private Shared Function Handler_WillPositionSheet(id as Ptr, s as Ptr, WindowHandle As Integer, SheetHandle As Integer, DefaultPosition As NSRect) as NSRect
  #Pragma Unused id
  #Pragma Unused s
  #Pragma Unused WindowHandle
  #Pragma Unused SheetHandle
  
  // Open the sheet 60 pixels below the natural position
  DefaultPosition.Top = DefaultPosition.Top - 60
  Return DefaultPosition
End Function

Now, here’s the funny thing about this code. It will not only affect all instances of this window, but it affects all windows! An improvement would be to implement this on a module. You can use WindowHandle to locate the window the action belongs to.