NSView subview backgrounds

  • Hi,

    I have an NSView subclass, which is getting a gradient background,
    drawn by NSBezierPaths.  I found that even though I'm drawing the
    background using colours with alpha component equal to 1.0, their
    colour is being effected by the colour of the object underneath it
    (which is black).  I can check that this is true by using the
    DigitalColor Meter application.

    I found a workaround, to fill the view with a background white before
    drawing the horizontal gradient paths. Unfortunately, when I add
    NSButtons with transparent backgrounds as subviews, they appear as
    white squares in the view. I've noticed that once I resize the
    window, so that the view redraws, the button appears correctly.
    However, after it is pressed and released, the white square reappears
    (the button is just a momentary-light button, so it should return to
    the initial state).

    Adding a [statusView setNeedsDisplay:YES]; after adding the button
    fixes the problem initially, but a button press apparently doesn't
    mark the button's rect as dirty, and the white background reappears.
    Furthermore, if there are many buttons, you can see the buttons'
    backgrounds change in sequence when they are added and
    setNeedsDisplay is called.  If I call setNeedsDisplay after adding
    all the buttons, you can see the incorrect background on each one
    briefly before they update.

    Could anyone suggest how to fix this?  I've attached my subclass'
    drawRect method:

    - (void)drawRect:(NSRect)rect
    {
        float steps = rect.size.height;

        [[NSColor whiteColor] set];
        [NSBezierPath fillRect:rect];

        // draw the gradient by horizontal bezier paths
        for (int i = 0; i < steps; i++)
        {
            NSColor *curColour = [NSColor
    colorByInterpolatingColor:initialColour

    withColor:finalColour
                                                    basedOnProgress:i
                                                outOfPossibleSteps:steps];
            curColour = [curColour colorWithAlphaComponent:1.0];
            NSBezierPath *path = [NSBezierPath bezierPath];
            [curColour setStroke];
            [path moveToPoint:NSMakePoint(0,i)];
            [path lineToPoint:NSMakePoint(rect.size.width,i)];
            [path setLineWidth:1.0];
            [path stroke];
        }

        // draw a black line along the bottom.
        NSBezierPath *path = [NSBezierPath bezierPath];
        [[NSColor blackColor] setStroke];
        [path moveToPoint:NSMakePoint(0,rect.size.height)];
        [path lineToPoint:NSMakePoint(rect.size.width,rect.size.height)];
        [path setLineWidth:1.0];
        [path stroke];

    }

    Thanks in advance,

    Josh
  • On Oct 1, 2007, at 10:42 AM, Twisted Theory Software wrote:

    > I have an NSView subclass, which is getting a gradient background,
    > drawn by NSBezierPaths.  I found that even though I'm drawing the
    > background using colours with alpha component equal to 1.0, their
    > colour is being effected by the colour of the object underneath it
    > (which is black).  I can check that this is true by using the
    > DigitalColor Meter application.

    Be careful how you interpret the results you get from DigitalColor
    Meter, they are the colors after the window server has done it's
    color matching, so you may not get the colors you expect, but they
    may also not be affected by the background...

    > I found a workaround, to fill the view with a background white
    > before drawing the horizontal gradient paths. Unfortunately, when I
    > add NSButtons with transparent backgrounds as subviews, they appear
    > as white squares in the view. I've noticed that once I resize the
    > window, so that the view redraws, the button appears correctly.
    > However, after it is pressed and released, the white square
    > reappears (the button is just a momentary-light button, so it
    > should return to the initial state).

    Are you sure that your embedding hierarchy is correct? You might just
    be getting lucky at some times and not others, and the white
    rectangle your drawing might just be mimicking what is already
    occurring in the window.

    > Could anyone suggest how to fix this?  I've attached my subclass'
    > drawRect method:

    The attached code is really not the best way to draw a gradient. You
    should look up the CGShading API for how to properly do gradients
    (and this route is also more performant than drawing a series of
    lines). This is due to get a lot easier in the future, but that
    future isn't here yet.
    --
    David Duncan
    Apple DTS Quartz and Printing
    <david.duncan...>
  • On Oct 1, 2007, at 12:42 PM, Twisted Theory Software wrote:

    > Hi,
    >
    > I have an NSView subclass, which is getting a gradient background,
    > drawn by NSBezierPaths.  I found that even though I'm drawing the
    > background using colours with alpha component equal to 1.0, their
    > colour is being effected by the colour of the object underneath it
    > (which is black).  I can check that this is true by using the
    > DigitalColor Meter application.
    >
    > Could anyone suggest how to fix this?  I've attached my subclass'
    > drawRect method:
    >
    > // draw the gradient by horizontal bezier paths
    > for (int i = 0; i < steps; i++)
    > {
    > NSColor *curColour = [NSColor
    > colorByInterpolatingColor:initialColour
    >
    > withColor:finalColour
    > basedOnProgress:i
    > outOfPossibleSteps:steps];
    > curColour = [curColour colorWithAlphaComponent:1.0];
    > NSBezierPath *path = [NSBezierPath bezierPath];
    > [curColour setStroke];
    > [path moveToPoint:NSMakePoint(0,i)];
    > [path lineToPoint:NSMakePoint(rect.size.width,i)];
    > [path setLineWidth:1.0];
    > [path stroke];
    > }

    Since the line will be centered on the division between pixels, there
    will be 0.5 of a pixel drawn on each side (i.e., it will end up being
    blurred  with the background).

    The quick fix is to do:
    [path moveToPoint:NSMakePoint(0,i+0.5)];
    [path lineToPoint:NSMakePoint(rect.size.width,i+0.5)];

    but the better fix is to use the lower level Quartz support for
    drawing gradients (which is much more efficient)

    Glenn Andreas                      <gandreas...>
      <http://www.gandreas.com/> wicked fun!
    quadrium2 | build, mutate, evolve, animate  | images, textures,
    fractals, art
  • On 10/1/07, glenn andreas <gandreas...> wrote:

    > but the better fix is to use the lower level Quartz support for
    > drawing gradients (which is much more efficient)

      Of course the easiest path is to google "CTGradient" ... this is a
    very handy class for drawing gradients.

    --
    I.S.
  • On 1 Oct, 2007, at 1:06 PM, David Duncan wrote:

    > On Oct 1, 2007, at 10:42 AM, Twisted Theory Software wrote:
    >
    >> I have an NSView subclass, which is getting a gradient background,
    >> drawn by NSBezierPaths.  I found that even though I'm drawing the
    >> background using colours with alpha component equal to 1.0, their
    >> colour is being effected by the colour of the object underneath it
    >> (which is black).  I can check that this is true by using the
    >> DigitalColor Meter application.
    >
    > Be careful how you interpret the results you get from DigitalColor
    > Meter, they are the colors after the window server has done it's
    > color matching, so you may not get the colors you expect, but they
    > may also not be affected by the background...

    What I mean is that between using a black background and drawing the
    white background, I can see, even visually, that the colours when
    using the white background are much lighter than without.

    >
    >> I found a workaround, to fill the view with a background white
    >> before drawing the horizontal gradient paths. Unfortunately, when
    >> I add NSButtons with transparent backgrounds as subviews, they
    >> appear as white squares in the view. I've noticed that once I
    >> resize the window, so that the view redraws, the button appears
    >> correctly.  However, after it is pressed and released, the white
    >> square reappears (the button is just a momentary-light button, so
    >> it should return to the initial state).
    >
    > Are you sure that your embedding hierarchy is correct? You might
    > just be getting lucky at some times and not others, and the white
    > rectangle your drawing might just be mimicking what is already
    > occurring in the window.

    I'm not sure, I don't know what an embedding hierarchy is.  However,
    even adding one button programmatically, by creating the button and
    then [statusView addSubview:buttonInstance] results in the white
    background problem, every time.  Again, setNeedsDisplay initially
    fixes the problem, but pressing the button instance causes it to
    reappear.  I've considered subclassing the button and adding a call
    to [superview setNeedsDislay:YES] to the mouseDown method, but this
    seems overly hacky.  I'm sure that this is a problem with my
    understanding of how views are drawn, which is minimal.

    When drawing a subview with a transparent background, why does the
    white colour, set with

    [[NSColor whiteColor] set];
    [NSBezierPath fillRect:rect];

    show up, and not the gradient background?

    Thanks,

    Josh
  • On 1 Oct, 2007, at 1:17 PM, glenn andreas wrote:

    >
    > On Oct 1, 2007, at 12:42 PM, Twisted Theory Software wrote:
    >
    >> Hi,
    >>
    >> I have an NSView subclass, which is getting a gradient background,
    >> drawn by NSBezierPaths.  I found that even though I'm drawing the
    >> background using colours with alpha component equal to 1.0, their
    >> colour is being effected by the colour of the object underneath it
    >> (which is black).  I can check that this is true by using the
    >> DigitalColor Meter application.
    >>
    >> Could anyone suggest how to fix this?  I've attached my subclass'
    >> drawRect method:
    >>
    >> // draw the gradient by horizontal bezier paths
    >> for (int i = 0; i < steps; i++)
    >> {
    >> NSColor *curColour = [NSColor
    >> colorByInterpolatingColor:initialColour
    >>
    >> withColor:finalColour
    >> basedOnProgress:i
    >>
    >> outOfPossibleSteps:steps];
    >> curColour = [curColour colorWithAlphaComponent:1.0];
    >> NSBezierPath *path = [NSBezierPath bezierPath];
    >> [curColour setStroke];
    >> [path moveToPoint:NSMakePoint(0,i)];
    >> [path lineToPoint:NSMakePoint(rect.size.width,i)];
    >> [path setLineWidth:1.0];
    >> [path stroke];
    >> }
    >
    > Since the line will be centered on the division between pixels,
    > there will be 0.5 of a pixel drawn on each side (i.e., it will end
    > up being blurred  with the background).
    >
    > The quick fix is to do:
    > [path moveToPoint:NSMakePoint(0,i+0.5)];
    > [path lineToPoint:NSMakePoint(rect.size.width,i+0.5)];
    >
    > but the better fix is to use the lower level Quartz support for
    > drawing gradients (which is much more efficient)
    >

    This seems to completely ignore the white colour background and
    blends the lines with the black background, which is the opposite of
    what I was after.  I'll look into the CGShading API, as suggested
    previously.

    Thanks,

    Josh
  • On Oct 1, 2007, at 11:22 AM, Twisted Theory Software wrote:

    > I'm not sure, I don't know what an embedding hierarchy is.

    What views are subviews of what other view, etc, down the line until
    you reach a window. You can check this in Interface Builder by
    putting the nib in list view and looking at what views are contained
    by what other views.

    If you want to get proper drawing behavior, then the buttons need to
    be contained by the view that is drawing the background.

    > When drawing a subview with a transparent background, why does the
    > white colour, set with
    >
    > [[NSColor whiteColor] set];
    > [NSBezierPath fillRect:rect];
    >
    > show up, and not the gradient background?

    [NSColor whiteColor] has an alpha value of 1.0, therefore drawing
    with it (in the default drawing mode) will obliterate what is in the
    drawn area. So basically what you are saying is "fill this view's
    area with white"

    Actually, I looked over your code again, and the problem turns out to
    be due to the way your using the rect passed into your -drawRect:
    method. This rect is constrained to the area that is actually being
    drawn, not the entire area of your view, but you are using it as
    such. You need to change your code to either use [self bounds] or to
    deal with redrawing subrects of the view instead of just the entire
    view. When I modified your code to do that everything worked correctly.
    --
    David Duncan
    Apple DTS Quartz and Printing
    <david.duncan...>
previous month october 2007 next month
MTWTFSS
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
Go to today