6 Stimmen

Kontextmenü nur für bestimmte Elemente in einer "Quellliste"

Ich habe ein Fenster mit einer Quellliste (NSOutlineView). Meine Quellliste hat nur zwei Ebenen. Ebene eins ist die Überschrift und Ebene zwei sind die Daten. Ich möchte ein Kontextmenü auf einigen der Datenzellen haben. Nicht auf allen.

Zuerst versuche ich, ein Menü an der Tabellenzellenansicht anzuhängen, die die Datenzelle repräsentiert -> es passiert nichts.

Zweitens bringe ich ein Menü in der Outline-Ansicht in IB an -> das Kontextmenü öffnet sich für jede Zelle (Überschrift und Daten). Ich versuche, das Öffnen des Menüs zu stoppen, finde aber nichts.

Hast du einige Ideen?

Vielen Dank

OS X 10.8.2 Lion, Xcode 4.5.2, SDK 10.8

7voto

Marc Charbonneau Punkte 40221

Wenn Sie NSOutlineView unterklasse, können Sie menuForEvent: überschreiben, um nur dann ein Menü zurückzugeben, wenn der Benutzer auf die richtige Zeile geklickt hat. Hier ist ein Beispiel:

- (NSMenu *)menuForEvent:(NSEvent *)event;
{
    // Das Ereignis enthält die Mausposition im Fensterbereich; konvertieren Sie sie in unseren (den Bereich der Übersichtansicht), damit wir feststellen können, auf welcher Zeile der Benutzer geklickt hat.
    NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
    NSInteger row = [self rowAtPoint:point];

    // Wenn der Benutzer nicht auf eine Zeile geklickt hat oder nicht genau eine Ebene unterhalb der obersten Hierarchieebene liegt, geben Sie nil zurück, d. h. kein Menü.
    if ( row == -1 || [self levelForRow:row] != 1 )
        return nil;

    // Menü erstellen und befüllen.
    NSMenu *menu = [[NSMenu alloc] init];
    NSMenuItem *delete = [menu addItemWithTitle:NSLocalizedString( @"Delete", @"" ) action:@selector(delete:) keyEquivalent:@""];

    [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];

    // Setzen Sie das repräsentierte Objekt des Menüelements "Delete" auf das angeklickte Element. Wenn der Benutzer dieses Element auswählt, erhalten wir sein repräsentiertes Objekt, sodass wir wissen, was gelöscht werden soll.
    [delete setRepresentedObject:[self itemAtRow:row]];

    return menu;
}

Dies setzt voraus, dass wir mit ARC kompilieren, sodass Sie das Menüobjekt, das erstellt wird, nicht freigeben müssen.

2voto

Patrick Smith Punkte 498

Diese Erweiterung + Unterklasse (beide NSOutlineView und NSTableView) tut das Vernünftige, indem sie prüft, ob einem Zell- oder Zeilenansicht ein Menü zugeordnet ist. Einfach eine allgemeine, wiederverwendbare Unterklasse!

Legen Sie das Menü auf der Zellansicht in outlineView:viewForTableColumn:item: fest - menu ist eine NSResponder-Eigenschaft.

(Der folgende Code ist in Swift)

// Eine Erweiterung ermöglicht es uns, sowohl NSTableView als auch NSOutlineView mit der gleichen Funktionalität zu unterklassifizieren
extension NSTableView {
    // Finde eine Zellansicht oder eine Zeilenansicht, die ein Menü hat. (z. B. Menü von NSResponder: NSMenu?)
    func burnt_menuForEventFromCellOrRowViews(event: NSEvent) -> NSMenu? {
        let point = convertPoint(event.locationInWindow, fromView: nil)
        let row = rowAtPoint(point)
        if row != -1 {
            if let rowView = rowViewAtRow(row, makeIfNecessary: true) as? NSTableRowView {
                let column = columnAtPoint(point)
                if column != -1 {
                    if let cellView = rowView.viewAtColumn(column) as? NSTableCellView {
                        if let cellMenu = cellView.menuForEvent(event) {
                            return cellMenu
                        }
                    }
                }

                if let rowMenu = rowView.menuForEvent(event) {
                    return rowMenu
                }
            }
        }

        return nil
    }
}

class OutlineView: NSOutlineView {
    override func menuForEvent(event: NSEvent) -> NSMenu? {
        // Aufgrund des seltsamen Verhaltens von NSTableView/NSOutlineView muss das Menü des Empfängers festgelegt werden, da sonst das Ziel nicht gefunden werden kann
        self.menu = burnt_menuForEventFromCellOrRowViews(event)

        return super.menuForEvent(event)
    }
}

class TableView: NSTableView {
    override func menuForEvent(event: NSEvent) -> NSMenu? {
        // Aufgrund des seltsamen Verhaltens von NSTableView/NSOutlineView muss das Menü des Empfängers festgelegt werden, da sonst das Ziel nicht gefunden werden kann
        self.menu = burnt_menuForEventFromCellOrRowViews(event)

        return super.menuForEvent(event)
    }
}

0voto

Mark Bernstein Punkte 2090

Es ist nicht klar, ob Ihr Gliederung eine Ansicht basiert ist oder Zell basiert. Das ist wichtig.

Wenn es sich um eine ansichtsbasierte Ansicht handelt, können Ihre Ansichtsinstanzen implementieren

- (NSMenu *)menuForEvent:(NSEvent *)theEvent

und das für dieses Element passende Menü zurückgeben - oder nil, wenn Sie überhaupt kein Menü möchten.

Wenn es sich um eine zellenbasierte Ansicht handelt oder wenn Sie dies aus irgendeinem Grund nicht in der Ansichtsklasse behandeln möchten, müssen Sie NSOutlineView untergeordnet sein und - (NSMenu *)menuForEvent:(NSEvent *)theEvent dort implementieren. Wieder werden Sie feststellen, welche Zelle getroffen oder aktiv ist, und von dort aus entscheiden, welches Menü Sie möchten.

0voto

Steve M Punkte 1194
- (void)rightMouseDown:(NSEvent *)event

Ein NSView gibt dies nicht an die nächste Ansicht weiter. Diese Methode überprüft, ob die aktuelle Klasse ein menuForEvent: hat, wenn dies der Fall ist, wird es aufgerufen. Wenn nicht, ist der Vorgang abgeschlossen und es passiert nichts mehr. Deshalb wird ein NSTableCellView nicht auf ein menuForEvent: reagieren, weil die Tabelle das rightMouseDown: absorbiert.

Sie können die TableView-Subklasse erstellen und das rightMouseDown: Ereignis behandeln und das rightMouseDown: von NSTableCellView aufrufen und Ihr Menü anzeigen, das Sie in Ihrem Storyboard erstellt und mit Ihrer NSTableViewCell verbunden haben.

Hier ist meine Lösung in einer untergeordneten NSTableView:

- (void)rightMouseDown:(NSEvent *)event
{
    for (NSTableRowView *rowView in self.subviews) {

        for (NSView *tableCellView in [rowView subviews]) {

            if (tableCellView) {
                NSPoint eventPoint = [event locationInWindow];
//            NSLog(@"Fensterpunkt:     %@", NSStringFromPoint(eventPoint));

                eventPoint = [self convertPoint:eventPoint toView:nil];
                eventPoint = [self convertPoint:eventPoint toView:self];
//            NSLog(@"Tabellenansichtspunkt: %@", NSStringFromPoint(eventPoint));

                NSRect newRect = [tableCellView convertRect:[tableCellView bounds] toView:self];
//            NSLog(@"Rechteck: %@", NSStringFromRect(newRect));

                BOOL rightMouseDownInTableCellView = [tableCellView mouse:eventPoint inRect:newRect];
//            NSLog(@"Maus in Ansicht: %hhd", mouseInView);

                if (rightMouseDownInTableCellView) {
                    if (tableCellView) {
                        // Seien wir sicher und stellen Sie sicher, dass das Objekt darauf reagieren wird.
                        if ([tableCellView respondsToSelector:@selector(rightMouseDown:)]) {
                            [tableCellView rightMouseDown:event];
                        }
                    }
                }
            }
        }
    }
}    

Dadurch wird festgestellt, wo das Rechtsklickevent aufgetreten ist, überprüft, ob die richtige Ansicht vorhanden ist, und leitet das rightMouseDown: an diese Ansicht weiter.

Bitte lassen Sie mich wissen, ob diese Lösung für Sie funktioniert.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X