如何管理动态大于可查看区域的NSView中的自定义数据(而不是照片)的绘制?

Phi*_*gan 0 cocoa objective-c

更新:相对于下面的问题和答案,似乎我NSView对与我试图绘制的自定义类和包装有关的类有误解NSScrollView.最后,我想弄清楚的是如何在面积大于可视区域的情况下管理自定义数据(而不是照片)的动态绘制NSView

我不是在寻找讲义,但我是Cocoa的新手,我认为我在Apple的文档上做了最好的练习,但似乎我已经弄错了基础知识.Apple的文档非常详细,技术性很强,完全围绕着处理照片,因此对我来说毫无用处.Apple提供的相关代码示例(例如Sketch)以典型的倾斜方式从打印机纸张尺寸中获取文档大小,这不是我需要的.我已经在网上搜索了教程,示例等,但是我找不到任何东西(我保证在我解决这个问题时写一个).

我正在从REALbasic移植这个代码,我完全使用它,即使使用撤消命令,但这样做的范例完全不同.这对我来说不是"点击".我很欣赏给予的帮助,我在这里仍然遗漏了一些东西,人们提供的其他任何东西都值得赞赏.

谢谢


我有一个子类NSView,我正在创建一个钢琴卷MIDI接口.我想解决一些问题:

  • 在滚动期间和之后绘制工件
  • 在滚动期间和之后不跨越可见区域的线
  • 在滚动时,有时在mouseDown上,水平滚动条跳转到右侧1(一)像素,但我还没有在任何地方实现scrollToPoint.

与上述有关的症状:

  • 实施adjustScroll会让事情变得更糟.
  • mouseDown会纠正所有问题,除非有时向右跳1像素.
  • 如果我取消注释NSLog命令,则drawRect的开头没有绘制.

Apple的文档提到像素精确的绘图,但(当然)没有提供如何实现这一点的例子.我一直在使用floor()函数来尝试获得一致的值,但是一旦我开始使用scrollToPoint或任何其他复杂性,事情就会变得混乱.

请以链接图像为例进行说明.屏幕截图,如果您能相信它,实际上清理了我在屏幕上看到的内容.在半透明度下,几乎所有地方都有双线.同样适用于我绘制的任何对象.

在滚动http://www.oatmealandcoffee.com/external/NSViewArtifacts.png后生成的子类NSView中的图形工件和不一致性

这是代码.我讨厌公开放弃这么多,但我到处寻找线索,如果互联网有任何迹象表明我是唯一有这个问题的人,我真的只想把这个问题整理好并继续前进.有很多,还有更多,但这些是我真正需要做的核心事情,坦率地说,我对如何纠正它感到茫然.

    - (void)drawRect:(NSRect)rect {
        //NSLog(@"OCEditorView:drawRect: START");

        [self setFrame:[[self EditorDocument] DocumentRect]];

        [[NSGraphicsContext currentContext] setShouldAntialias:NO];

        // CLEAR BACKGROUND

        [[[self EditorDocument] ColorWhiteKey] set];
        NSRectFill(rect);

        // BACKGROUND KEYS

        int firstRowLine = 0; //NSMinY(rect); //<- adding the function results in bad spacing on scrolling
        int currentRowLine = 0;
        int lastRowLine = NSMaxY(rect);

        //NSLog(@"lastRowLine:%d", lastRowLine);

        float currentZoomY = [self ZoomY];

        for (currentRowLine = firstRowLine; currentRowLine <= lastRowLine; currentRowLine += currentZoomY) {

            int currentTone = floor(currentRowLine / [self ZoomY]);
            BOOL isBlackKey = [[self MusicLib] IsBlackKey:currentTone];

            //NSLog(@"%d, tone:%d, black:%d", [self MusicLib], currentTone, isBlackKey);

            if (isBlackKey) {
                [[[self EditorDocument] ColorBlackKey] set];
            } else {
                [[NSColor whiteColor] set];
            }

            NSBezierPath *rowLine = [NSBezierPath bezierPath];

            NSPoint bottomLeftPoint = NSMakePoint(NSMinX(rect), currentRowLine);
            NSPoint bottomRightPoint = NSMakePoint(NSMaxX(rect), currentRowLine);
            NSPoint topRightPoint = NSMakePoint(NSMaxX(rect), currentRowLine + [self ZoomY]);
            NSPoint topLeftPoint = NSMakePoint(NSMinX(rect), currentRowLine + [self ZoomY]);

            [rowLine moveToPoint:bottomLeftPoint];
            [rowLine lineToPoint:bottomRightPoint];
            [rowLine lineToPoint:topRightPoint];
            [rowLine lineToPoint:topLeftPoint];

            [rowLine closePath];

            [rowLine fill];

            BOOL isOctave = [[self MusicLib] IsOctave:currentTone];
            if (isOctave) {
                [[[self EditorDocument] ColorXGrid] set];

                NSBezierPath *octaveLine = [NSBezierPath bezierPath];
                NSPoint leftPoint = NSMakePoint(NSMinX(rect), currentRowLine);
                NSPoint rightPoint = NSMakePoint(NSMaxX(rect), currentRowLine);
                [octaveLine moveToPoint:leftPoint];
                [octaveLine lineToPoint:rightPoint];
                [octaveLine stroke];
            }
        } 

        // BACKGROUND MEASURES

        //[[self EditorDocument].ColorYGrid setStroke];

        int firstColumnLine = 0;
        int currentColumnLine = 0;
        int lastColumnLine = NSMaxX(rect);

        int snapToValueInBeats = [[self EditorDocument] SnapToValue];
        int snapToValueInPixels = floor(snapToValueInBeats * [self ZoomX]);
        int measureUnitInBeats = floor([[self EditorDocument] TimeSignatureBeatsPerMeasure] * [[self EditorDocument] TimeSignatureBasicBeat]);
        int measureUnitInPixels = floor(measureUnitInBeats * [self ZoomX]);

        for (currentColumnLine = firstColumnLine; currentColumnLine <= lastColumnLine; currentColumnLine += snapToValueInPixels) {

            //int currentBeat = floor(currentColumnLine / [self ZoomX]);
            int isAMeasure = currentColumnLine % measureUnitInPixels;
            int isAtSnap = currentColumnLine % snapToValueInPixels;

            if ((isAMeasure == 0) || (isAtSnap == 0)) {

                if (isAtSnap == 0) { 
                    [[NSColor whiteColor] set];                 
                }

                if (isAMeasure == 0) { 
                    [[[self EditorDocument] ColorXGrid] set]; 
                }

                NSBezierPath *columnLine = [NSBezierPath bezierPath];

                NSPoint startPoint = NSMakePoint(currentColumnLine, NSMinY(rect));
                NSPoint endPoint = NSMakePoint(currentColumnLine, NSMaxY(rect));

                [columnLine moveToPoint:startPoint];
                [columnLine lineToPoint:endPoint];

                [columnLine setLineWidth:1.0];
                [columnLine stroke];

            } // isAMeasure or isAtSnap
         } // currentColumnLine

        // NOTES

        for (OCNoteObject *note in [[self EditorDocument] Notes]) {

            OCNoteObject *currentNote = note;

            NSRect noteBounds = [self GetRectFromNote:currentNote];
            //NSLog(@"noteBounds:%d", noteBounds);

            // set the color for the note fill
            // this will have to come from the parent Track

            NSMutableArray *trackColors = [self EditorDocument].TrackColors;

            if (note.Selected) {
                [[trackColors objectAtIndex:0] set];
            } else {
                [[trackColors objectAtIndex:1] set];
            }

            [NSBezierPath fillRect:noteBounds];

            // outline

            [[NSColor blackColor] set];
            [NSBezierPath strokeRect:noteBounds];

         } // for each note

        /*
        if (EditorController.startingUpApplication == YES) {
            [self setDefaultSettingForApplicationStartUp];
        }
         */
    //NSLog(@"OCEditorView:drawRect: END"); 
    }

- (void)mouseDown:(NSEvent *)theEvent {

    //NSLog(@"OCEditorObject:mouseDown: START");

    // This converts the click into coordinates
    MouseDownPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];

    // Calculate the beat and pitch clicked into...

    float startBeat = floor(MouseDownPoint.x / [self ZoomX]);
    float pitch = floor(MouseDownPoint.y / [self ZoomY]);
    float length = [[self EditorDocument] NewNoteLength];

    //NSLog(@"X:%f, Y:%f", MouseDownPoint.x, MouseDownPoint.y);
    //NSLog(@"beat:%f, pitch:%f", startBeat, pitch);

    LastDragPoint = MouseDownPoint; // save the point just in case.

    OCNoteObject *note = [self GetClickedNoteFromPoint:MouseDownPoint];

    if ([EditorController EditorMode] == AddObjectMode) {

        //NSLog(@"AddObjectMode)");

        float snapToX = [[self EditorDocument] SnapToValue];
        float snappedStartBeat = floor(startBeat / snapToX) * snapToX;

        //NSLog(@"%f = %f / %f * %f", snappedStartBeat, startBeat, snapToX, snapToX);

        OCNoteObject *newNote = [[self EditorDocument] CreateNote:snappedStartBeat Pitch:pitch Length:length];
        //NSLog(@"newNote:%d", newNote);

        [newNote Deselect];

    } else if ([EditorController EditorMode] == EditObjectMode) {

        //NSLog(@"EditObjectMode");

        // if nothing was clicked, then clear the selections
        // else if the shift key was pressed, add to the selection

        if (note == nil) {
            [self SelectNone];  
        } else {

            //NSLog(@"mouseDown note.pitch:%f, oldPitch:%f", note.Pitch, note.OldPitch);

            BOOL editingSelection = (([theEvent modifierFlags] & NSShiftKeyMask) ? YES : NO);
            if (editingSelection) {
                if (note.Selected) {
                    [self RemoveFromSelection:note];
                } else {
                    [self AddToSelection:note];
                }
            } else {
                if (note.Selected) {
                    // do nothing
                } else {
                    [self SelectNone];
                    [self AddToSelection:note];
                }
            }

            [self SetOldData];

        } // (note == nil)

    } else if ([EditorController EditorMode] == DeleteObjectMode) {

        if (note != nil) {
            [self RemoveFromSelection:note];
            [[self EditorDocument] DestroyNote:note];
        } // (note != nil)

    } // EditorMode

    [self setFrame:[[self EditorDocument] DocumentRect]];
    [self setNeedsDisplay:YES];
}

- (void)mouseDragged:(NSEvent *)theEvent {
    //NSLog(@"mouseDragged");

    NSPoint currentDragPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
    // NSLog(@"currentDragPoint: %d", currentDragPoint)

    float snapToValueInBeats = [[self EditorDocument] SnapToValue];

    int deltaXinPixels = floor(currentDragPoint.x - MouseDownPoint.x);
    int deltaYinPixels = floor(currentDragPoint.y - MouseDownPoint.y);

    int deltaXinBeats = floor(deltaXinPixels / [self ZoomX]);
    int deltaY = floor(deltaYinPixels / [self ZoomY]);

    int deltaX = floor(deltaXinBeats / snapToValueInBeats) * snapToValueInBeats;

        for (OCNoteObject *note in [self Selection]) {
            [self MoveNote:note DeltaX:deltaX DeltaY:deltaY];       
        }

    LastDragPoint = currentDragPoint;

    [self autoscroll:theEvent];

    [self setNeedsDisplay:YES]; //artifacts are left if this is off.
}

- (void)mouseUp:(NSEvent *)theEvent {
    if ([EditorController EditorMode] == AddObjectMode) {

    } else if ([EditorController EditorMode] == EditObjectMode) {

    } else if ([EditorController EditorMode] == DeleteObjectMode) {

    }

    [self setNeedsDisplay:YES];
}
Run Code Online (Sandbox Code Playgroud)

我很可能会遗漏一些明显的东西,但我认为我太接近代码,无法看到它的解决方案.任何帮助是极大的赞赏!谢谢!

Nik*_*uhe 6

我认为你误解了方式drawRect:及其论证的作用:

drawRect:只要您的视图或部分视图需要重绘,cocoa就会发送消息.CGRect参数是当前重绘的所有更新区域的边界框.这意味着您不应从此矩形派生视图中任何对象的位置.它只传递给方法以允许优化绘图:如果某些内容完全在此矩形之外,则不需要重绘.

您应该从视图坐标系计算视图中的所有位置:[self bounds].每次drawRect:执行时都不会更改,并为您提供视图内容的原点和大小.

您的代码还有其他一些问题(例如,不要setFrame:从内部调用drawRect:)但我认为您应首先获得正确的坐标,然后进一步研究如何计算矩形的像素对齐坐标.