Selectively preventing titlebar drag

  1. last week

    Thom M

    Jun 25 Greater Hartford Area, CT

    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.
    -image-

    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.

    #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
  2. @Thom M — 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.

  3. Thom M

    Jun 26 Greater Hartford Area, CT

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

  4. Kevin G

    Jun 26 Xojo Pro Gatesheed, England

    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

  5. 6 days ago

    Thom M

    Jun 28 Greater Hartford Area, CT

    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.

  6. Kevin G

    Jun 28 Xojo Pro Gatesheed, England

    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

    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
  7. Jonathan A

    Jun 28 Testers Maryland, USA

    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.

  8. Thom M

    Jun 28 Greater Hartford Area, CT

    @JonathanAshwell 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.

    @Kevin G 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
    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

    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.

  9. Thom M

    Jun 28 Greater Hartford Area, CT

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

  10. 5 days ago

    Sam R

    Jun 28 Testers, Xojo Pro, Third Party Store Hengchun, Pingtung, Taiwan

    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.

or Sign Up to reply!