Drag border vs FocusRing, visual cues and drop file selected

Recently I was told I did not know what I was talking about when I noticed a change in focus seemingly could not occur while a file drag was happening in Cocoa. Ignorance is not a sin, and only idiots could pretend knowing everything. So the only conclusion was that I needed to learn more about that wondrous aspect of things.

I maintain that it is impossible to change the focus in a Cocoa app while a file is dragged, after numerous experiments setting the focus to different controls while the file drag was happening, both in DragEnter and via a timer or via MouseMove or MouseEnter. In all these cases, the Focus Ring did not appear before the file was actually dropped and the file drag operation ended.

Nevertheless, I recall having witnessed applications showing a box around a control when a file drag occurred, as soon as the dragged object hovered over the control. Lately, I noticed the App Wrapper window showed a blue frame when an app was dragged over.

I had made the hypothesis that the Focus Ring not switching was a Cocoa issue, so I ran the same project I had made for Cocoa in Carbon mode and made two discoveries.

  • SetFocus in DragEnter immediately creates a focus ring around the focused control : Canvas, TextField
  • Even without any additional code but an AcceptFileDrop, a Canvas or a BevelButton shows a box around the frame the instant a file is dragged over. But it is not a focus ring ! It is a 2 pixels wide GREY rectangle along the border of the canvas, akin to what I used to do in DragEnter as workaround. I tried to look around for the exact terminology, but could not find anything so I call that “drag border”. Here is how it looks. Once again, no code in DragEnter at all. It is obvious this is not a focus ring, as the Textfield above the canvas exhibits a splendid Focus Ring, while the canvas shows the drag box.

Cocoa does not have a drag border, at least in Xojo. Some brilliant minds may locate in Apple documentation that feature, but in the meantime, applying a workaround to emulate Carbon behavior looks fair to my modest comprehension… I may not know much, but I place providing appropriate functionalities to the user above being always right.

I am reposting here the workaround I use so it can be found more easily :

It works fine with me.SetFocus in the DragEvent. But indeed, what is strange is that although a beep, for instance, takes effect immediately, the focus ring appears only after the file has been dropped. One would like the focus ring to appear immediately.

I tried the declare above, but the same occurs.

To get the focus ring immediately when I drag a file over, here is what I do :

Function DragOver(x As Integer, y As Integer, obj As DragItem, action As Integer) As Boolean dim p as new picture(me.width,me.height,32) p.Transparent = 1 p.graphics.ForeColor=&c4EE3FF00 p.graphics.PenHeight=2 p.graphics.PenWidth=2 p.graphics.DrawRect(0,0,me.width,me.height) me.backdrop = p End Function

and this removes the ring when the object is out of the canvas.

Sub DragExit(obj As DragItem, action As Integer) me.backdrop = nil End Sub

Of course to avoid interferences, I set the canvas UseFocusRing to false in the inspector.

FWIW - I could not get your workaround to work in my Cocoa app. I noticed, as you reported, there is no change to the canvas when a dragitem is held over the canvas. I turned off useFocusRing and implemented your code. I stlll do not see any indication when a drag item is held over the canvas.
Xojo 2014r2
Mac OS 10.9.2

I use in Cocoa in Canvas DragOver

  declare function NSClassFromString lib "Cocoa" ( inName as CFStringRef ) as Ptr
  declare function sharedApplication lib "Cocoa" selector "sharedApplication" ( classRef as Ptr ) as Ptr
  Dim myApplication as Ptr =  sharedApplication( NSClassFromString( "NSApplication" ) )
  declare sub activateIgnoringOtherApps lib "Cocoa" selector "activateIgnoringOtherApps:" ( appRef as Ptr, flag as boolean )
  activateIgnoringOtherApps( myApplication, True )
  me.AcceptFocus = True
  me.UseFocusRing = True
  me.SetFocus

then you have a focus ring on drag & drop

in DropObject

your code
+
me.UseFocusRing = False

Here is a short project I just tested for you which works as indicated with the code posted :
DragMe.xojo_binary_project

Your declare does indeed set the focus on the canvas, but only *after * the file has been dropped. Just like a regular me.SetFocus.

What I posted shows a drag border as soon as the drag object hovers the canvas, just like it happens in Carbon.
Drag border <> Focus ring. See my first post picture, where the TextField has the focus, and the grey drag border is present in the canvas. I call it drag border for not having been able to find mention of that in Apple documentation. Could be drag frame, drag box, whatever.

I have no reason to doubt you and see it works on your system. If the canvas is the only control on the ‘drop a picture file’ window, it usually has the focus automatically when the window is activated. You may want to place a TextField with TabIndex 0 on the same window to make sure the focus is on another control before you drag the file.

Mavericks 10.9.4 here. 2014 R2. I assure you that I placed your code in DragEnter and the focus ring appears only after the file has been dropped. Repeated it multiple times. Could it be some sort of system inconsistency or a question of Xojo version ?

Or maybe you could post a short project for test purposes ?

here it is

scalepictureOSX

I got it ! It is so simple, and yet unexpected. While you where posting your project, I was working on my implementation of your code. Everything I was dropping in there did not trigger the Focus Ring, but after the file had been dropped. Until I realized your app deals with picture files.

Sure enough, when I drag picture files over my app, bam, the Focus Ring appears as soon as the dragged file is over the Canvas.

It is not a question of system version or Xojo version, it is a question of file type. When dragging a picture file, Focus Ring shows up immediately. Other types hold the focus change until the file has been dropped. And we could not see the same thing as I used other file types, and you where of course using only picture files. Mystery solved.

Sometimes, the mysteries of the Mac OS X Cocoa framework are baffling.

Thank you for posting your movie, it got me on the track.

Axel, I finally understood how your declare works.

Reading it, I could not understand why it never referred to the Canvas handle, as one woud expect if it was doing something to it. But it did seem to be related to the app priority over other running apps.

I am using on the same window three different methods : Drag Border, Declare (your code), and simple UseFocusRing = True and Me.SetFocus in DragEnter for the last one.

If I click away from the app the window gets in the background. My code shows the rectangle fine without activating the window, and it activates when the file is dropped. Your code activates the window when the dragged file gets over the canvas, and the focus takes place. The simple code without your declares to activate the window works only after one clicks on it to activate it, and then fetches the file without deactivating the app’s window.

So your code is of interest beyond the simple drag file issue : it can be extremely useful to activate an app simply when the mouse cursor gets over a window, instead of having to click into it. This is great ! You should place it into Xippets. I remembers seing threads about this kind of feature.

Thank you, Michel, for posting the small project for me to try. After several attempts, I realized that the reason it wasn’t working for my project while it did in your example is that I have code in my paint event to scale and display the picture dropped, If I commented out that paint event code, I got your focus ring. But I must have that paint event code to display the dropped pic, so I guess I’ll have to try another solution.
Thanks to Michel and Axel for this valuable discussion.

I have not seen your code, but all I do is to create a picture and place it as the canvas backdrop before the file is dropped. From what you describe, you scale the picture and display it after it has been dropped. Unless the code in your paint event displays a default picture that overrides the drag border, I do not see why it should interfere. Maybe you could post the code you are using, if it is not too confidential ?

An alternative would be to use a window boolean property set to true in DragEnter (let us call it ‘dragging’) that holds your code until you set it to false in DragExit. Then in Paint you start by

if dragging is true then return

So as long as the dragged file is over the canvas, the code in Paint does not execute, and the drag border displays just as if you comment out the code.

Roger, I just realize we are talking about pictures you need to drop. Use Axel code and it will work just fine.

I did. It doesn’t.

Experimenting a bit more with your code, Michel, I discovered that it is the presence of a picture in the canvas which prevents the backdrop pic from working.

I have a Window property teamPic As picture

In the DropObject event of the canvas, I have…

me.backdrop = nil if obj.PictureAvailable Then teamPic = obj.Picture me.Invalidate elseif obj.FolderItemAvailable Then teamPic = Picture.Open(obj.FolderItem) me.Invalidate end if

in the Paint event I have…

if teamPic <> Nil Then g.DrawPicture teamPic, 0, 0, me.Width, me.Height, 0, 0, teamPic.Width, teamPic.Height end if

I changed those parts of your example project and tried it. The first time teamPic was Nil, so the Paint event did nothing and I saw the focus ring. Subsequent drops, however, did not display the focus ring as teamPic was not nil and, apparently, the paint event prohibited the backdrop from being set.
I need to use the paint event because my users will be dropping files of various sizes on this canvas and they will expect the pics to be scale appropriately.

Add’l info: I tried setting teamPic = Nil at the beginning of the dropObject event. The picture went blank, but I did see the focus ring each time. I may experiment a bit more with this to see if it will work for me.

I have worked on the problem from the code you posted. My code assumed that there is nothing in the paint event. In order to safeguard the existing image, I

  • added a teampicframed picture property
  • modified DragEnter, DragExit, DropObject and Paint as follows :

Function DragEnter(obj As DragItem, action As Integer) As Boolean dim p as new picture(me.width,me.height,32) if teampic <> nil then teampicframed = new picture(me.width,me.height,32) teampicframed.graphics.DrawPicture teamPic, 0, 0, me.Width, me.Height, 0, 0, teamPic.Width, teamPic.Height teampicframed.Transparent = 1 teampicframed.graphics.ForeColor=&c4EE3FF00 teampicframed.graphics.PenHeight=2 teampicframed.graphics.PenWidth=2 teampicframed.graphics.DrawRect(0,0,me.width,me.height) me.invalidate else teampic = new picture(me.width,me.height,32) teampic.Transparent = 1 teampic.graphics.ForeColor=&c4EE3FF00 teampic.graphics.PenHeight=2 teampic.graphics.PenWidth=2 teampic.graphics.DrawRect(0,0,me.width,me.height) me.invalidate end if End Function

Sub DragExit(obj As DragItem, action As Integer) if teampicframed = nil then teampic = nil end if teampicframed = nil me.invalidate End Sub

Sub DropObject(obj As DragItem, action As Integer) me.backdrop = nil if obj.PictureAvailable Then teamPic = obj.Picture me.Invalidate elseif obj.FolderItemAvailable Then teamPic = Picture.Open(obj.FolderItem) teampicframed = nil me.Invalidate end if End Sub

Sub Paint(g As Graphics, areas() As REALbasic.Rect) if teamPic <> Nil and teampicframed = nil Then g.DrawPicture teamPic, 0, 0, me.Width, me.Height, 0, 0, teamPic.Width, teamPic.Height elseif teamPic <> Nil and teampicframed <> nil Then g.DrawPicture teamPicframed, 0, 0 end if End Sub

Now the drag border appears in all cases and disappears after the file has been dropped, or if the drag is aborted.

I will PM you the project.

In a message I posted yesterday to the attention of Axel, I noted :

Bug report “34553 - Focus ring misbehavior after a file drag and drop operation” shows that the focus ring appears only once when the file is dragged over when SetFocus is changed in DragEvent. I thought it could be because the focus could not show when the window was inactive. But that is not the case. Even when the window is kept selected the focus ring appears once, and then don’t. Indeed there is something happening here that prevents the simple SetFocus to take place more than once. Hopefully it can be fixed by Xojo in a future release.

In the meantime, the method is unusable.

The “works once” issue in the bug report 34553 with the SetFocus method is more complex than it seems, and does have to do with the application active state. After a series of experimentations, I discovered what is going on, and it affects Axel code as well. Here is the manipulation :

  • Run the project I posted. Make sure the window is active.
  • Drag and drop a picture file into either “Declare” or “SetFocus” ; Focus ring shows fine
  • Make sure the window stays active and drop another file ; Focus ring does not show.

As long as the window stays active after the first drop, the Focus Ring does not appear again. In order for it to manifest again, the application has to be deactivated, and activated again. Either with Axel’s declare, or by clicking in the window.

This explains why in the movie Axel posted, it works every time : when he goes to the “Bilder” folder on the right to pick the file, it deactivates the application. When he drags the file over, it activates the application again, so the Focus Ring appears fine. So in order for the SetFocus method to work every time, it is necessary for the application to be deactivated after each drop.

I tried to add another window to the application and switch between them, no luck. Simply deactivating the window within the program does not do. The entire application has to go in the background and back to front for the Focus Ring to show.

I tried to set a custom cursor, but probably because already the drag folder image cursor is a custom one, it does not work. So instead I used a canvas tucked outside of the window edge (left = -40) containing a small 14x14 symbol picture, which I display next to the cursor in DragOver. It is not as easy as a custom cursor, because the size of the folder icon can vary : if you drag a small icon, it stays small. And if you drag a big icon, it stays the same size. So the symbol has to be far enough on the right not to be covered by the dragged document icon. It works fine, though, and I hide back the canvas in DragExit and DropObject.

Small symbol picture (red icon with a white cross) file (right click to download)

Function DragOver(x As Integer, y As Integer, obj As DragItem, action As Integer) As Boolean redcross.left = system.mouseX-self.left+25 //redcross is a small 14x14 canvas redcross.top = system.mousey-self.top End Function

Add in DragExit, DropObject :

redcross.left = -40

With the same technique, you can add all sorts of visual indicators about the file being dropped, depending on the information available through obj in DragEnter. You can also display tooltip-like messages in Window.DragEnter, and then change them in Canvas.DragEnter. I already use that in MouseEnter and MouseExit of some apps. The advantage of doing this is that the message displays immediately, whereas a tooltip has some latency.