views:

764

answers:

2

I'm customizing the appearance of a WinForms ToolTip control by responding to the Draw event. I just want some of the ToolTip's corners to be rounded. I've got everything working such that the first time the ToolTip is displayed, everything looks perfect. On subsequent displays, however, the unfilled areas of my rounded rectangle continue to have what was in the background the first time the ToolTip was displayed.

Screen shot of problem (I don't have rights to put inline apparently): http://tinypic.com/r/30xa3w9/3

In the picture, you can see the left-over artifacts in the upper-left corner where I would like it to just be transparent (showing the gray background), like this:

tinypic.com/r/mvn8eo/3 (nor rights to add more than one link)

Here is the drawing code:

private void ToolTip_Draw(object sender, DrawToolTipEventArgs args)
{
    args.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    var rect = new RectangleF(0, 0, args.Bounds.Width, args.Bounds.Height);
    using (var backBrush = new LinearGradientBrush(rect, Color.Silver, this.BackColor, 90))
    {
        using (var path = GetRoundedRectangle(rect, 10, 4, 4, 1))
        {
            args.Graphics.FillPath(backBrush, path);
            args.DrawText();
        }
    }
}

The GetRoundedRectangle function (not included) just calculates the appropriate GraphicsPath for the rounded geometry that I want.

I tried adding a call to args.DrawBackground after setting the BackColor to Color.Transparent, but that just filled in the area with the dark gray of the form's background rather than actually being transparent, which I think is the typical "simulated" transparency of WinForms.

As a side note, an non-customized ToolTip with IsBalloon set to true is non-rectangular with correct transparency.

Can anyone suggest a fix for this problem?

Thanks, Eric

+1  A: 

Control.Region is what you are looking for. You need to tell the window manager the shape of the tooltip, so background is properly redrawn.

Filip Navara
A ToolTip isn't a Control, though, it is just a Component so it doesn't have the Region property. Is there maybe some indirect way of doing this?
Eric Smith
@Eric Smith, hmm, it has a Handle property and you can use SetWindowRgn (see pinvoke.net) on that. It may be possible to do it in completely managed code with the help of NativeWindow class, but it's probably more work than it is worth.
Filip Navara
I tried the SetWindowRgn approach and got it to work. The big problem there is that the region isn't smoothed, so the tooltip has unpleasantly jagged edges. From what I read elsewhere, smoothing a region may not be possible.
Eric Smith
It is not possible with the regions, but on Windows 2000 and newer it is possible to achieve with layered windows - http://msdn.microsoft.com/en-us/library/ms997507.aspx
Filip Navara
A: 

Here is a solution, though imperfect. It uses Graphics.CopyFromScreen to copy the area under the tooltip into the background. Of course, getting the location of the tooltip isn't simple -- hence the reflection and PInvoke call to GetWindowRect.

A remaining glitch is that the background might be wrong while the the tooltip fades out. For example, if you have a button that is colored when the mouse is over it, the tooltip will still have that colored background when you move the mouse off while it fades away. Setting ToolTip.UseFading to false seems to change the frequency of background paints such that it is worse than the fading problem. If the user has disabled eye candy at the OS level, that might also trigger the same paint glitches as having UseFading set to false.

    private void ToolTip_Draw2(object sender, DrawToolTipEventArgs args)
 {
  var graphics = args.Graphics;
  var bounds = args.Bounds;
  graphics.SmoothingMode = SmoothingMode.AntiAlias;
  var windowRect = GetWindowRect();
  graphics.CopyFromScreen(windowRect.Left, windowRect.Top, 0, 0, new Size(bounds.Width, bounds.Height));

  using (var backBrush = new LinearGradientBrush(bounds, C.Color_LogitechGray2, this.BackColor, 90))
  {
   using (var path = GetRoundedRectangle(bounds, 10, 4, 4, 1))
   {
    args.Graphics.FillPath(backBrush, path);
    args.DrawText();
   }
  }
 }

 [DllImport("user32.dll")]
 [return: MarshalAs(UnmanagedType.Bool)]
 static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);

 private Rectangle GetWindowRect()
 {
  RECT rect = new RECT();
  var window = typeof(ToolTip).GetField("window", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(this) as NativeWindow;
  GetWindowRect(window.Handle, ref rect);
  return rect;
 }
Eric Smith