Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

0002378

Suspended
Original poster
May 28, 2017
675
671
For left-clicks, there is the "action" event handler hook. For double-clicks, there is "doubleAction". For right-clicks, there is ... nothing ?

I'm trying to reassure myself that I haven't done enough research yet, but did the Apple developers conveniently forget to write a simple detection mechanism for an NSTableView right click that gives me the row/column that was clicked ? Or did they just think it would be funny ?

There is NSTableViewDelegate.tableView(...didClick...), but that doesn't even get triggered by my clicks (left or right).

There is NSTableView.clickedRow, but that is almost no good, because I have to call that myself after the fact. I want to receive the notification, as soon as the click is performed, that "Hey, you right-clicked the table view, and here is the row you right-clicked."

From the research I have done, it looks like implementing the low-level rightMouseDown() event, and figuring out the row from X/Y co-ordinates is what I have to do ? And I tried doing that, with some really weird results. If this is what I have to do, it is a cruel joke.

Am I out of luck ?
 

chown33

Moderator
Staff member
Aug 9, 2009
10,885
8,692
A sea of green
"Right click" is a Windows-ism. The Mac-ism is "contextual click" or "contextual menu" (from olden times when a mouse might not have 2 buttons). It can be mapped to a physical right-click but absent a 2nd buttons it's mapped to a click with the CONTROL modifier key pressed.
Handy mnemonic: CONTrol modifier : CONTextual menu

Next, knowing that it's called a contextual menu, I googled nstableview contextual menu and found the following:
https://stackoverflow.com/questions...when-you-right-click-on-a-cell-of-nstableview

Extract of top answer:
There's no need to muck about with event handling, all you have to do to assign a contextual menu is set the table view's menu outlet to point to the NSMenu object that you want to use for the contextual menu.

You can do this in Interface Builder by dropping an NSMenu object into your nib file and control-dragging from the table view to the menu to set the outlet.

Alternatively, you can use the -setMenu: method of NSTableView (inherited from NSResponder) to assign the menu programmatically.
If you want to do something other than present a contextual menu with a right-click contextual click, then you will probably run into a lot more difficulty.
 
  • Like
Reactions: 0002378

0002378

Suspended
Original poster
May 28, 2017
675
671
"Right click" is a Windows-ism. The Mac-ism is "contextual click" or "contextual menu" (from olden times when a mouse might not have 2 buttons). It can be mapped to a physical right-click but absent a 2nd buttons it's mapped to a click with the CONTROL modifier key pressed.
Handy mnemonic: CONTrol modifier : CONTextual menu

Next, knowing that it's called a contextual menu, I googled nstableview contextual menu and found the following:
https://stackoverflow.com/questions...when-you-right-click-on-a-cell-of-nstableview

Extract of top answer:
There's no need to muck about with event handling, all you have to do to assign a contextual menu is set the table view's menu outlet to point to the NSMenu object that you want to use for the contextual menu.

You can do this in Interface Builder by dropping an NSMenu object into your nib file and control-dragging from the table view to the menu to set the outlet.

Alternatively, you can use the -setMenu: method of NSTableView (inherited from NSResponder) to assign the menu programmatically.
If you want to do something other than present a contextual menu with a right-click contextual click, then you will probably run into a lot more difficulty.

Thanks, that's good information. I didn't know about the origin of contextual menus.

So, I did already know about presenting a contextual menu and I already had that working. Now, the reason I wanted to have this right-click event notification, in addition to presenting the menu, was that, the menu's items will depend on which row of which table view was clicked, i.e. it is a dynamic menu.

I have 4 different table views in my app, and the menu's appearance (and functionality) will depend on which view, and which row, was clicked. So, I need to determine that information before the menu is presented.

Currently, I have achieved this by doing the following:

Code:
// Pseudocode

// ---- In my custom NSTableView subclass ------

override rightMouseDown() {

// THIS IS A KIND OF UGLY HACK
// Take note of which the 4 views was right-clicked by storing
// a reference to "self" (this table view) somewhere outside this class
}

// ------- In my NSMenuDelegate for the contextual menu ------

func menuWillOpen() {

// Retrieve that info about 1 - which table view was right-clicked,
// and 2 - its "clickedRow" field value, which is now available,  and
// use both pieces of info to prepare the menu for display, just in time
}

So, to summarize, I need both a contextual menu (which is trivial to implement, as you pointed out and I discovered), and (ideally) also a way to know beforehand, which row of which view was clicked.

But, as you mentioned, and I have strong confidence in your knowledge of this stuff, this is far less straightforward than simply presenting a menu. So, I will stick with my hack for now. Thanks again !
 
Last edited:

Krevnik

macrumors 601
Sep 8, 2003
4,101
1,312
Or, another option is to setup a delegate of the NSMenu that implements the logic you need. It would also need to know about the table view, but it could request the clicked row while it is doing the update, which will be right before it becomes visible to the user.
 
  • Like
Reactions: chown33 and 0002378

0002378

Suspended
Original poster
May 28, 2017
675
671
Or, another option is to setup a delegate of the NSMenu that implements the logic you need. It would also need to know about the table view, but it could request the clicked row while it is doing the update, which will be right before it becomes visible to the user.

Right, this is exactly what I'm doing (see code block above) in MyMenuDelegate.menuWillOpen().

BTW, do you know how to disable right-click row highlights ? See image below (I don't want that ugly highlight rectangle). I got this image from a StackOverflow page asking the same question ... the solutions there are worthless.

I've tried just about everything I can think of, short of entirely rendering the friggin NSTableView myself.

FeJex.jpg
 

0002378

Suspended
Original poster
May 28, 2017
675
671
Ok, I finally solved both problems - 1: Calculating the right-clicked row, and 2: disabling the ugly row highlight when the contextual menu is displayed.

Only took about 5 hours when it should have taken about 5 minutes.
 

Krevnik

macrumors 601
Sep 8, 2003
4,101
1,312
Right, this is exactly what I'm doing (see code block above) in MyMenuDelegate.menuWillOpen().

That's what I get for writing the post, and then posting hours later. Sorry about that.

Ok, I finally solved both problems - 1: Calculating the right-clicked row, and 2: disabling the ugly row highlight when the contextual menu is displayed.

Only took about 5 hours when it should have taken about 5 minutes.

Yeah, the unfortunate part of AppKit is how esoteric it is in places. There's quite a few places where getting two bits of behavior glued together is left as an exercise to the reader, even though they are common.
 

0002378

Suspended
Original poster
May 28, 2017
675
671
That's what I get for writing the post, and then posting hours later. Sorry about that.

No worries. It's good to know that you would approach it the same way ... gives me confidence in my own solution.

Yeah, the unfortunate part of AppKit is how esoteric it is in places. There's quite a few places where getting two bits of behavior glued together is left as an exercise to the reader, even though they are common.

Yup, that is unfortunate. And, it's not like Java where you can readily run through the JDK source in the debugger to see what the heck is going on behind the scenes.

BTW, you probably already know this, but, in order to disable that row highlight (when the menu is presented), in the end, I simply had to override NSTableView.menu(for event: NSEvent), and return the menu myself instead of letting the NSTableView implementation of that function be called. It is responsible for drawing that highlight.

I would have loved just stepping through the AppKit code in the debugger. Oh, well :)

Thank God a community like this (and StackOverflow) exist !
 
Last edited:

0002378

Suspended
Original poster
May 28, 2017
675
671
For anyone curious, here's my solution. And, to be honest, I'm quite happy with it :D

Code:
/*
    A customized NSTableView that overrides contextual menu behavior
 */
class MyTableView: NSTableView {
 
    /*
        This function needs to be overriden in order to:
 
        1 - Only display the contextual menu when at least one row is available
        2 - Capture the row for which the contextual menu was requested, and select it
        3 - Disable the row highlight displayed when presenting the contextual menu
     */
    override func menu(for event: NSEvent) -> NSMenu? {
        return menuHandler(for: event)
    }
}

extension NSTableView {
 
    func menuHandler(for event: NSEvent) -> NSMenu? {
 
        // If tableView has no rows, don't show a contextual menu
        if (self.numberOfRows == 0) {
            return nil
        }
 
        // Calculate the clicked row
        let row = self.row(at: self.convert(event.locationInWindow, from: nil))

        // If the click occurred outside of any of the playlist rows (i.e. empty space), don't show the menu
        if (row == -1) {
            return nil
        }
 
        // Select the clicked row, implicitly clearing the previous selection
        self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
 
        // Note that this view was clicked (this is required by the contextual menu)
        // TableViewContext is a utility class that stores an instance of NSTableView, which will be accessed by the menu delegate just prior to displaying the menu.

        TableViewContext.noteViewClicked(self)
 
        return self.menu
    }

/*
    Delegate for the contextual menu displayed when a tableView item is right-clicked
 */
class TableContextualMenuDelegate: NSObject, NSMenuDelegate {
    …
    // Sets up the menu items that need to be displayed, depending on what type of item was clicked, and the current state of that item
    func menuNeedsUpdate(_ menu: NSMenu) {

       let clickedView = TableViewContext.clickedView  
       let clickedItem = TableViewContext.clickedItem

       // Set up my menu
    }
}

}
 
Last edited:

chown33

Moderator
Staff member
Aug 9, 2009
10,885
8,692
A sea of green
For anyone curious, here's my solution. And, to be honest, I'm quite happy with it :D

Code:
/*
    A customized NSTableView that overrides contextual menu behavior
 */
class MyTableView: NSTableView {
 
    /*
        This function needs to be overriden in order to:
 
        1 - Only display the contextual menu when at least one row is available
        2 - Capture the row for which the contextual menu was requested, and select it
        3 - Disable the row highlight displayed when presenting the contextual menu
     */
    override func menu(for event: NSEvent) -> NSMenu? {
        return menuHandler(for: event)
    }
}

extension NSTableView {
 
    func menuHandler(for event: NSEvent) -> NSMenu? {
 
        // If tableView has no rows, don't show a contextual menu
        if (self.numberOfRows == 0) {
            return nil
        }
 
        // Calculate the clicked row
        let row = self.row(at: self.convert(event.locationInWindow, from: nil))

        // If the click occurred outside of any of the playlist rows (i.e. empty space), don't show the menu
        if (row == -1) {
            return nil
        }
 
        // Select the clicked row, implicitly clearing the previous selection
        self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
 
        // Note that this view was clicked (this is required by the contextual menu)
        // TableViewContext is a utility class that stores an instance of NSTableView, which will be accessed by the menu delegate just prior to displaying the menu.

        TableViewContext.noteViewClicked(self)
 
        return self.menu
    }

/*
    Delegate for the contextual menu displayed when a tableView item is right-clicked
 */
class TableContextualMenuDelegate: NSObject, NSMenuDelegate {
    …
    // Sets up the menu items that need to be displayed, depending on what type of item was clicked, and the current state of that item
    func menuNeedsUpdate(_ menu: NSMenu) {

       let clickedView = TableViewContext.clickedView 
       let clickedItem = TableViewContext.clickedItem

       // Set up my menu
    }
}

}
It's unclear why you need both a subclass (MyTableView) and a class extenstion of NSTableView. Why not just a subclass?

The func menuHandler() isn't an override (given the absence of 'override' keyword), so there's no logical reason it needs to be an extension of NSTableView. If an NSTableView, as distinct from a subclass of it, needs access to a menuHandler() func, please explain why.

If menuHandler() doesn't need to be an extension of NSTableView, then it can reside in the subclass MyTableView. This is a more logical placement, because it puts the function in the class that needs it, i.e. the subclass with new functionality. In short, unless menuHandler() is intended to replace existing functionality within an unsubclassed NSTableView, then it should go in the subclass where it's needed.


I also wonder about TableContextualMenuDelegate, but there's not enough code posted to evaluate.

The thing I wonder about is the comment where it says "depending on what type of item was clicked". In general, if you have a generalized function that needs to examine an object's type in order to decide what to do, it suggests refactoring into specialized objects that are specifically tailored to each type, and thus don't need to go through the step of determining type. Over-generalization or under-specialization may lead to the Big Ball of Mud or sometimes Small Balls of Mud aka dung-balls.

Maybe I'm reading too much into the word "type", since I'm taking it in the technical sense of a primitive type or an object that's an instance of a particular class. Code would have clarified that.
 

0002378

Suspended
Original poster
May 28, 2017
675
671
It's unclear why you need both a subclass (MyTableView) and a class extenstion of NSTableView. Why not just a subclass?

Good question. Your confusion arises from the fact that I omitted a lot of the code that is specific to my app. I have 3 outline views and one table view that all need the same menu handling logic. So, I put it in an extension, to avoid code duplication. So, in my app code, there are 2 custom subclasses (not just the one I posted here) - one table view subclass and one outline view subclass, both of which simply defer to the extension (they have no other code in them except the call to the extension func). Also, the outline views have top level items (groups) and children ... and the context menu will differ depending on which type of item is clicked. That's what I meant by item "type".

In any case, thanks for the tip about type specialization. Will look into it.
 
Last edited:

Krevnik

macrumors 601
Sep 8, 2003
4,101
1,312
BTW, you probably already know this, but, in order to disable that row highlight (when the menu is presented), in the end, I simply had to override NSTableView.menu(for event: NSEvent), and return the menu myself instead of letting the NSTableView implementation of that function be called. It is responsible for drawing that highlight.

It has been a while since I have done this level of overriding on Mac, but rereading the docs makes me think the right-click is also selecting the row. The row view or cell normally owns its behavior for highlight/selection, and Mac only has the concept of selection as far as I can tell. The screenshot makes me think you are using source list selection style in the table?

In any case, thanks for the tip about type specialization. Will look into it.

Yeah, this is a good case for attaching the behavior to a subclass. It can wind up applying the behavior to Apple-owned tables by accident otherwise.
 

0002378

Suspended
Original poster
May 28, 2017
675
671
Yeah, this is a good case for attaching the behavior to a subclass. It can wind up applying the behavior to Apple-owned tables by accident otherwise.

Actually, that's not true. I have given my function a custom name "menuHandler", which doesn't already exist (otherwise, I wouldn't have been able to name it that without the override keyword). So, that means that no existing Apple code even knows it exists. Which means it can never be accidentally invoked by Apple code, until the highly unlikely event that they someday decide to augment their APIs with a function of the same name and signature, in which case my code will automatically stop compiling (it will tell me I need to use the override keyword now) and I will be alerted to the danger you mentioned. This is a non-issue :)

In other words, the new menu handler function would have to be invoked through my own code ... it will never be invoked directly by Apple code.

For clarity through exaggeration, let's say I added a function called "gimmeSomeAppleSeeds()" to an extension of NSTableView. No Apple code in the world is going to know it exists, so it will never be able to invoke that function. Right ?

The danger you mentioned would be applicable to when a superclass function is overriden (by a subclass), rather than when an entirely new function is defined (in an extension).

Furthermore, as I mentioned above, I put the custom handler in an extension because I have both a custom table view and a custom outline view that both need the exact same code (this is not obvious in my code block above for the simple reason that I don't want to paste all my app code here). To avoid code duplication, I put it in an extension. I could have done it another way, like put it in a fileprivate closure variable and reused that instead. I chose one way of doing it.
 
Last edited:

Krevnik

macrumors 601
Sep 8, 2003
4,101
1,312
Actually, that's not true. I have given my function a custom name "menuHandler", which doesn't already exist (otherwise, I wouldn't have been able to name it that without the override keyword). So, that means that no existing Apple code even knows it exists. Which means it can never be accidentally invoked by Apple code, until the highly unlikely event that they someday decide to augment their APIs with a function of the same name and signature, in which case my code will automatically stop compiling (it will tell me I need to use the override keyword now) and I will be alerted to the danger you mentioned. This is a non-issue :)

Fair point. You are correct in this case. I mis-read the 'override menu' piece of code.

The danger you mentioned would be applicable to when a superclass function is overriden (by a subclass), rather than when an entirely new function is defined (in an extension).

Which by subclassing, removes the danger. So that's not a problem either.

Furthermore, as I mentioned above, I put the custom handler in an extension because I have both a custom table view and a custom outline view that both need the exact same code (this is not obvious in my code block above for the simple reason that I don't want to paste all my app code here). To avoid code duplication, I put it in an extension. I could have done it another way, like put it in a fileprivate closure variable and reused that instead. I chose one way of doing it.

Hmm, yes, re-reading the code, I can see why you did it that way, but it still comes across as a bit backwards. The common piece is the delegate, and the behavior you want is tied to the menu you want to display, not the table view, per se. The delegate is also a great place to store context as well, since that's where behavior for the menu is being delegated to, rather than using a utility class that effectively acts like a singleton and bottlenecks your UI interactions. That can bite you down the road.

What I'd personally be doing is more along the lines of this:

Code:
class TableContextualMenuDelegate: NSObject, NSMenuDelegate {
    // A completely immutable type, internal to the delegate.
    // This would replace the singleton context object, and instead
    // each delegate would be responsible for its own clicked item state.
    struct ClickedItem {
         let view
         let tableItem

         init(... some nice parameter here ...) { ... configure view/tableItem here ... }
    }

    private var clickedItem? : ClickedItem

    ... snip ...

    // This is specific to this delegate, and contains what used to be
    // in "func menuHandler(for event: NSEvent) -> NSMenu?"
    // Also, you can probably avoid selecting the rows here, too. You can
    // give ClickedItem the TableView and IndexSet and let it figure out
    // how to fetch the things you want during init, or even lazily.
    func menuWillAppear(for event: NSEvent, table: NSTableView) {
        ... calculate and store self.clickedItem ...
    }

    func menuNeedsUpdate(_ menu: NSMenu) {
        guard let clickedView = self.clickedItem?.view else { ... handle error here ... }
        guard let clickedItem = self.clickedItem?.tableItem else { ... handle error here ... }

        // Set up the menu
    }
}

class MyTableView: NSTableView {
    override func menu(for event: NSEvent) -> NSMenu? {
        guard let menuDelegate = self.menu.delegate as TableContextualMenuDelegate else { return nil }

        menuDelegate.menuWillAppear(for: event, table: self)
        return self.menu
    }
}
 
  • Like
Reactions: 0002378

0002378

Suspended
Original poster
May 28, 2017
675
671
Hmm, yes, re-reading the code, I can see why you did it that way, but it still comes across as a bit backwards. The common piece is the delegate, and the behavior you want is tied to the menu you want to display, not the table view, per se. The delegate is also a great place to store context as well, since that's where behavior for the menu is being delegated to, rather than using a utility class that effectively acts like a singleton and bottlenecks your UI interactions. That can bite you down the road.

What I'd personally be doing is more along the lines of this:

Great, thanks. I'll take a look. Why would a util singleton bottleneck my UI interactions ?
 

Krevnik

macrumors 601
Sep 8, 2003
4,101
1,312
Why would a util singleton bottleneck my UI interactions ?

Singletons are always a bottleneck of some kind. Every point in code that references the singleton is passing control of data through that bottleneck. In this case, the information about the click event so that it reaches the menu's delegate from the TableView handling the event. Only one write/read pair can reliably use the singleton at a time, setting it up for a race condition.

As written, you can get away with this, but it doesn't change the potential for the underlying race condition to turn into a bug with consequences for the user. More references to the singleton, longer periods of time between the write and the last read, etc all make the race condition more likely to be a real problem rather than one on paper. And considering things like using dispatch_async() into the main queue as a way to delay side effects are something Apple likes to use, there's always the small risk that Apple changes the timing and exposes the race condition for you. So, the fun part is that race conditions, while they are considered a multi-thread problem, can be a single-thread problem these days because of tools like GCD and Apple's reliance on them.

Keep in mind when I'm pointing stuff out like this, you may be thinking I'm picking nits, and you may be right. But I usually point them out precisely because I've seen that pattern before in shipping code where an unpicked nit became a source for new bugs.
 

0002378

Suspended
Original poster
May 28, 2017
675
671
Only one write/read pair can reliably use the singleton at a time, setting it up for a race condition.

I understand your concern, and it is good to consider these scenarios, so I appreciate you pointing this out. But, I'm absolutely certain that this will not be an issue in this particular app.

I think I belong to a different breed of developers; I think most would agree with you. I, on the other hand, like, very much, not to over-engineer or over-complicate my solutions. I keep my use cases in mind (and yes, future use cases too), and the complexity of my solution is usually proportional to the complexity of the particular use case/problem and the potential negative impact of "bad code". And, to me, the best solution is the one that fits the target use case(s) best, with zero excess complexity. In fact, I think that, quite often, "future-proofing" for unimaginable cases is unnecessary and wasteful (time, effort), and I have seen many a developer wasting time writing code that is astronomically unlikely, if not impossible, to ever be invoked.

Again, I belong to a different school of thought. I don't deem it necessary to code for cases whose probability is damn near 0%, unless I'm working on an Airbus autopilot system that holds the lives of people in its hands, in which case the potential negative impact is huge (lives lost). Solution complexity proportional to problem complexity and potential negative impact. On the other hand, if the maximum possible negative impact is a menu not showing properly 1/10000 times it is invoked, and there is an easy solution - re-invoking the menu, then I'd rather spend that one extra hour working on a new feature. I hope the essence of what I'm trying to convey is clear :)

In other words, there will never ever, in the lifetime of the app for which this code was written, be a situation in which more than one contextual menu will need to be displayed simultaneously. In fact, I don't even think that is theoretically possible. So, I'm quite certain a singleton will suffice for this use case.

My approach may seem silly or careless to you (and most others), but I really don't see a need to over-complicate any piece of code. I'd rather spend the extra time thinking about or working on something else that provides more value :)

Again, this is not a criticism of your approach, but rather, just an explanation of mine. Hope I didn't come off as critical.
 
Last edited:

Krevnik

macrumors 601
Sep 8, 2003
4,101
1,312
Oh, I totally am in the camp of not over engineering things. I don't like to add a ton of stuff onto my code for eventualities that I haven't designed for. I'll go address those when I get to it.

However, I don't think this is a case of over/under engineering. Seriously, you could take the Singleton and shove it in the menu delegate as a public variable and call it a day. You could even remove the singleton entirely and move the functions/data into the delegate. Now you just have the view and the delegate, and no third entity complicating the logic.

If you want to talk simplicity vs complexity, the singleton should simply not exist. It is itself the added complexity you are espousing to avoid. It adds complexity in this case, both in the form of additional locations within your code that you need to reason about to know how a menu makes it to the screen, and in terms of having an additional type whose sole purpose is to move data between two objects that already know about each other. That to me is over engineering it. :)

Really, the big reason I get on peoples' cases about this, is because these sort of anti-patterns become habits. And habits are hard to break. And in my own history, the Singleton as an anti-pattern is an especially painful one. Especially since CoreData by its design makes Singletons so freaking tempting to use.
 

0002378

Suspended
Original poster
May 28, 2017
675
671
If you want to talk simplicity vs complexity, the singleton should simply not exist. It is itself the added complexity you are espousing to avoid. It adds complexity in this case, both in the form of additional locations within your code that you need to reason about to know how a menu makes it to the screen, and in terms of having an additional type whose sole purpose is to move data between two objects that already know about each other. That to me is over engineering it. :)

I think we are approaching the territory of personal preference, rather than the objective measure of complexity/simplicity (which doesn't exist). And so, I'd rather not argue about it.

What seems simple to me may seem incredibly complex to you, given that we have different programming styles and have different knowledge/skill bases. For me, having that Singleton the way I have it, is the simplest possible approach.

I think that measuring, objectively, the simplicity/complexity of a piece of code, is a futile exercise, for the reason I mentioned. Complexity and simplicity, in my opinion, are perceived (by the programmer), making them highly subjective, rather than objective.

Proof ? If complexity and simplicity were able to be measured objectively, why would we need design meetings ? Why would we need software architects ? We could write programs to write programs to write programs. Well, clearly, that is not the case in the real world. Humans make judgment calls, and other humans often disagree, but one of them puts his/her foot down, "We're going to do it this way because I'm top gun." :)

Now, if you're talking about complexity in terms of low-level machine instructions, you win, because I have no idea which of the solutions in question translates to the fewest/simplest machine instructions. But, to me, the code I have written is the simplest possible solution for this problem, from my perspective as a (fledgling) Swift developer.
 
Last edited:
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.