Selectively preventing titlebar drag

I’m designing a window that uses a full window design like Music, App Store, Messages, etc. but I need to prevent window dragging only on certain parts of the transparent titlebar. There will be buttons there, and currently clicking on them still allows the window to be moved despite returning true from MouseDown, because the I assume the window gets the click before Xojo does.

Here’s an example, I want to allow normal behavior in the green areas, prevent dragging in the red areas.

MBS is an option for this. I’ve tried setting NSWindowMBS.Movable to false in MouseDown, which would have been awful if it worked, but it doesn’t. I suspect MouseDown is too late in the chain. I’ve tried getting the event With NSWindwoMBS.CurrentEvent and that works, but I don’t have a way to cancel the event. I don’t think that’s the right call anyway, I believe I would need to return nil from a subclass’ overridden method.

Anybody have any advice?

If it helps, this is how I get the window looking the way it does.

[code]#if TargetMacOS
Var Win As NSWindowMBS = Self.NSWindowMBS
Win.StyleMask = Win.StyleMask Or NSWindowMBS.NSFullSizeContentViewWindowMask
Win.TitlebarAppearsTransparent = True
Win.TitleVisibility = NSWindowMBS.NSWindowTitleHidden

Var Toolbar As New NSToolbarMBS(“com.thezaz.beacon.mainwindow.toolbar”)
Toolbar.sizeMode = NSToolbarMBS.NSToolbarDisplayModeIconOnly
Toolbar.showsBaselineSeparator = False
Self.mToolbar = Toolbar

Win.toolbar = Toolbar
#endif[/code]

@Thom McGrath — Not sure if this will help you or complicate things further, but you can intercept events (mouse events in this case) before they are processed and you can also modify or just cancel them. So you should be able to determine if a mouse-click is happening in one “forbidden” area or not and act accordingly.

In order to do that, call the NSEvent.addLocalMonitorForEventsMatchingMask:handler: selector. “Handler” is a Block containing the code to execute. It gets the “id” (i.e. Ptr) for the NSEvent. You can modify the event and return it, or you can return nil to block the event entirely.

I don’t have MBS so I can’t help you with how to implement that, but I am sure you will figure this out.

Thanks for the tip, I’ll look into that.

There are two ways to capture events:

  1. NSEventMonitorMBS
    2.CGEventTapMBS

We use both of them and from what I can remember, you might need CGEventTapMBS to detect clicks in the window’s title bar.
Apple have been locking CGEventTap down in recent releases so use the version that accepts the process ID in the constructor as this allows you to pass the process ID of your application (NSRunningApplicationMBS.currentApplication.ProcessIdentifier).
These settings might also be useful to you:
tapLocation = CGEventTapMBS.kCGAnnotatedSessionEventTap
tapPlace = CGEventTapMBS.kCGHeadInsertEventTap

Unfortunately, I may be out of luck on this one. Neither of these ideas panned out. NSEventMonitor doesn’t help because I can intercept the mouse down before it gets to Xojo, but it still doesn’t prevent the titlebar drag. CGEventTap, even when limited to my process, requests system accessibility access from the user, and doesn’t help for the same reason NSEventMonitor doesn’t help.

Here’s some very rough code i’ve just hacked up that should work on macOS 10.11 and later.
NOTE. You will need to tidy up the events listed during the initialisation and in the event handler.

Class Window1
Inherits Window

Window1.Open:
Sub Open()
'prevent window dragging by default
Declare Sub setMovable Lib "AppKit" Selector "setMovable:" (windowHandle As Integer, enabled As Boolean)
setMovable(Self.Handle, False)

'attach our event monitor listener so that we can enable window dragging in specific areas 
Self.mCocoaEventMonitorObj_ = New cCocoaWindowEventMonitor
Call Self.mCocoaEventMonitorObj_.addLocalMonitorForEventsMatchingMask(Bitwise.BitOr(NSEventMBS.NSLeftMouseDown Mask, NSEventMBS.NSLeftMouseDraggedMask, NSEventMBS.NSLeftMouseUp, NSEventMBS.NSRightMouseDownMask, NSEventMBS.NSRightMouseDraggedMask, NSEventMBS.NSRightMouseUp, NSEventMBS.NSOtherMouseDownMask, NSEventMBS.NSOtherMouseDraggedMask, NSEventMBS.NSOtherMouseUp)) 
End Sub

Private mCocoaEventMonitorObj_ As cCocoaWindowEventMonitor 
End Class

Class cCocoaWindowEventMonitor
Inherits NSEventMonitorMBS

[code]
cCocoaWindowEventMonitor.LocalEvent:
Function LocalEvent(e as NSEventMBS) As NSEventMBS
Declare sub performWindowDragWithEvent Lib “AppKit” Selector “performWindowDragWithEvent:” (windowHandle As Integer, eventHandle As Integer)

Dim userWindowClicked As Window
Dim count As Int32
Dim i As Int32
Dim clickY As Int32

'determine if we have clicked in a user window (ie: not an alert)
userWindowClicked = Nil
count = WindowCount - 1
For i = 0 To count
If Window(i).NSWindowMBS = e.window Then
userWindowClicked = Window(i)
Exit For
End If
Next

'continue if we have clicked in a user window
If userWindowClicked <> Nil Then
If (e.type = NSEventMBS.NSLeftMouseDown) Or (e.type = NSEventMBS.NSRightMouseDown) Or (e.type = NSEventMB S.NSOtherMouseDown) Then
clickY = userWindowClicked.Bounds.Height - e.locationInWindow.Y
If clickY <= (userWindowClicked.Top - userWindowClicked.Bounds.Top) Then
'clicked in the title bar
If e.locationInWindow.x > 200 Then
'clicked in an area we allow dragging so start the drag
performWindowDragWithEvent(e.window.Handle, e.Handle)
End If
End If
End If
End If

'return the event unmodified
Return e
End Function
End Class[/code]

I had a similar problem with canvases in a “unified” Xojo window. I placed the code to disable/enable dragging in the canvas’ MouseEnter and MouseExit events.

Now THAT is an idea. As I mentioned I tried setting movable to false on MouseDown, but it’s too late. Using MouseMove to turn it off before the user even clicks is probably the simplest way to solve this problem.

[quote=494272:@Kevin Gale]Here’s some very rough code i’ve just hacked up that should work on macOS 10.11 and later.
NOTE. You will need to tidy up the events listed during the initialisation and in the event handler.

Class Window1
Inherits Window

Window1.Open:
Sub Open()
'prevent window dragging by default
Declare Sub setMovable Lib "AppKit" Selector "setMovable:" (windowHandle As Integer, enabled As Boolean)
setMovable(Self.Handle, False)

'attach our event monitor listener so that we can enable window dragging in specific areas 
Self.mCocoaEventMonitorObj_ = New cCocoaWindowEventMonitor
Call Self.mCocoaEventMonitorObj_.addLocalMonitorForEventsMatchingMask(Bitwise.BitOr(NSEventMBS.NSLeftMouseDown Mask, NSEventMBS.NSLeftMouseDraggedMask, NSEventMBS.NSLeftMouseUp, NSEventMBS.NSRightMouseDownMask, NSEventMBS.NSRightMouseDraggedMask, NSEventMBS.NSRightMouseUp, NSEventMBS.NSOtherMouseDownMask, NSEventMBS.NSOtherMouseDraggedMask, NSEventMBS.NSOtherMouseUp)) 
End Sub

Private mCocoaEventMonitorObj_ As cCocoaWindowEventMonitor 
End Class

Class cCocoaWindowEventMonitor
Inherits NSEventMonitorMBS

[code]
cCocoaWindowEventMonitor.LocalEvent:
Function LocalEvent(e as NSEventMBS) As NSEventMBS
Declare sub performWindowDragWithEvent Lib “AppKit” Selector “performWindowDragWithEvent:” (windowHandle As Integer, eventHandle As Integer)

Dim userWindowClicked As Window
Dim count As Int32
Dim i As Int32
Dim clickY As Int32

'determine if we have clicked in a user window (ie: not an alert)
userWindowClicked = Nil
count = WindowCount - 1
For i = 0 To count
If Window(i).NSWindowMBS = e.window Then
userWindowClicked = Window(i)
Exit For
End If
Next

'continue if we have clicked in a user window
If userWindowClicked <> Nil Then
If (e.type = NSEventMBS.NSLeftMouseDown) Or (e.type = NSEventMBS.NSRightMouseDown) Or (e.type = NSEventMB S.NSOtherMouseDown) Then
clickY = userWindowClicked.Bounds.Height - e.locationInWindow.Y
If clickY <= (userWindowClicked.Top - userWindowClicked.Bounds.Top) Then
'clicked in the title bar
If e.locationInWindow.x > 200 Then
'clicked in an area we allow dragging so start the drag
performWindowDragWithEvent(e.window.Handle, e.Handle)
End If
End If
End If
End If

'return the event unmodified
Return e
End Function
End Class[/code][/quote]

This is also a clever idea. I was approaching the problem backwards, trying to stop the click when necessary. My concern with this code is it assumes dragging is the only thing possible from the titlebar. Things like a double click to minimize based on system settings would end up being blocked. So I was going to take the idea and set Movable to false in the mouse down from NSEventMonitor, but I have a feeling using Xojo’s MouseMove will work and be simpler. Thank you though, it gives me another possibility if the mouse move idea doesn’t work.

Just an update, setting Movable in MouseMove seems to work flawlessly. Thanks for the idea @Jonathan Ashwell!

I had to solve just this, and I did it by putting a canvas in the areas where I didn’t want it to be dragged, on the mouse enter event, I disable drag and mouse exit, re-enable it.