views:

326

answers:

2

I'm making a sample GUI for a new application we are developing at work. The language has already been decided for me, but I am allowed to use any 3rd party DLLs or add-ins or whatever I need in order to make the GUI work as seamlessly as possible.

They want it very mac/ubuntu/vista/Windows 7-like, so I've come up with some very interesting controls and pretty GUI features. One of which are some growing/shrinking buttons near the top of the screen that increase in size when you mouse over them (it uses the distance formula to calculate the size it needs to increase by). When you take your mouse off of the controls, they shrink back down. The effect looks very professional and flashy, except that there is a ghosting effect as the button shrinks back down (and the buttons to the right of it since they are fixed-at-the-hip).

Here is what the buttons look like in the designer:

alt text

Here are some code snippets that I am using to do this:

pops child buttons underneath when parent is hovered

Private Sub buttonPop(ByVal sender As Object, ByVal e As System.EventArgs)
    For Each control As System.Windows.Forms.Control In Me.Controls
        If control.GetType.ToString = "Glass.GlassButton" AndAlso control.Location.Y > sender.Location.Y AndAlso control.Location.X >= sender.Location.X AndAlso control.Width < sender.Width AndAlso control.Location.X + control.Width < sender.Location.X + sender.Width Then
            control.Visible = True
        End If
    Next
End Sub

size large buttons back to normal after mouse leaves

Private Sub shrinkpop(ByVal sender As Object, ByVal e As System.EventArgs)
    Dim oldSize As Size = sender.Size
    sender.Size = New Size(60, 60)
    For Each control As System.Windows.Forms.Control In Me.Controls
        If control.GetType.ToString = "Glass.GlassButton" AndAlso control.Location.X > sender.Location.X AndAlso (Not control.Location.X = control.Location.X + (sender.size.width - oldSize.Width)) Then

            control.Location = New Point(control.Location.X + (sender.size.width - oldSize.Width), control.Location.Y)

        End If
        If control.GetType.ToString = "Glass.GlassButton" AndAlso control.Location.Y > sender.Location.Y AndAlso control.Location.X = sender.Location.X AndAlso control.Width < sender.Width Then
            control.Location = New Point(control.Location.X, control.Location.Y + (sender.size.height - oldSize.Height))
            If Windows.Forms.Control.MousePosition.X < control.Location.X Or Windows.Forms.Control.MousePosition.X > control.Location.X + control.Width Then
                control.Visible = False
            End If
        End If
    Next
End Sub

increase size of large command buttons based on the mouse location in the button, happens on mouse move

    Private Sub buttonMouseMovement(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
    Dim oldSize As Size = sender.Size
    Dim middle As Point = New Point(30, 30)
    Dim adder As Double = Math.Pow(Math.Pow(middle.X - e.X, 2) + Math.Pow(middle.Y - e.Y, 2), 0.5)
    Dim total As Double = Math.Pow(1800, 0.5)

    adder = (1 - (adder / total)) * 20

    If Not (sender.size.width = 60 + adder And sender.size.height = 60 + adder) Then
        sender.Size = New Size(60 + adder, 60 + adder)
    End If
    For Each control As System.Windows.Forms.Control In Me.Controls
        If control.GetType.ToString = "Glass.GlassButton" AndAlso control.Location.X > sender.Location.X AndAlso (Not control.Location.X = control.Location.X + (sender.size.width - oldSize.Width)) Then
            control.Location = New Point(control.Location.X + (sender.size.width - oldSize.Width), control.Location.Y)
        End If
        If control.GetType.ToString = "Glass.GlassButton" AndAlso control.Location.Y > sender.Location.Y AndAlso control.Location.X >= sender.Location.X AndAlso control.Width < sender.Width AndAlso control.Location.X + control.Width < sender.Location.X + sender.Width AndAlso (Not control.Location.Y = control.Location.Y + (sender.size.height - oldSize.Height)) Then
            control.Location = New Point(control.Location.X, control.Location.Y + (sender.size.height - oldSize.Height))
        End If
    Next
End Sub

increase size of smaller command buttons

Private Sub SmallButtonMouseMovement(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs)
    Dim oldSize As Size = sender.Size
    Dim middle As Point = New Point(22.5, 22.5)
    Dim adder As Double = Math.Pow(Math.Pow(middle.X - e.X, 2) + Math.Pow(middle.Y - e.Y, 2), 0.5)
    Dim total As Double = Math.Pow(1012.5, 0.5)

    adder = (1 - (adder / total)) * 15

    If Not (sender.size.Width = 45 + adder And sender.size.height = 45 + adder) Then
        sender.Size = New Size(45 + adder, 45 + adder)
    End If
    For Each control As System.Windows.Forms.Control In Me.Controls
        If control.GetType.ToString = "Glass.GlassButton" AndAlso control.Location.Y > sender.Location.Y AndAlso control.Location.X = sender.location.X AndAlso (Not control.Location.Y = control.Location.Y + (sender.size.height - oldSize.Height)) Then
            control.Location = New Point(control.Location.X, control.Location.Y + (sender.size.height - oldSize.Height))
        End If
    Next
End Sub

decrease puts command buttons back to correct location and hides them if appropriate

    Private Sub SmallShrinkPop(ByVal sender As Object, ByVal e As System.EventArgs)
    Dim oldsize As Size = sender.Size
    If Not (sender.size.width = 45 AndAlso sender.size.height = 45) Then
        sender.size = New Size(45, 45)
    End If

    Dim ChildCounter As Integer = 0

    For Each control As System.Windows.Forms.Control In Me.Controls
        If control.GetType.ToString = "Glass.GlassButton" AndAlso control.Location.X = sender.location.X AndAlso control.Width = sender.width AndAlso control.Location.Y > sender.location.y Then
            ChildCounter += 1
            control.Location = New Point(control.Location.X, control.Location.Y + (sender.size.height - oldsize.Height))
            If Windows.Forms.Control.MousePosition.X < control.Location.X Or Windows.Forms.Control.MousePosition.X > control.Location.X + control.Width Then
                sender.visible = False
                control.Visible = False
            End If
        End If
    Next
    If (ChildCounter = 0 AndAlso Windows.Forms.Control.MousePosition.Y > sender.Location.Y + sender.Height) Or (Windows.Forms.Control.MousePosition.X < sender.Location.X Or Windows.Forms.Control.MousePosition.X > sender.Location.X + sender.Width) Then
        sender.visible = False
        For Each control As System.Windows.Forms.Control In Me.Controls
            If control.GetType.ToString = "Glass.GlassButton" AndAlso control.Location.X = sender.location.x AndAlso control.Width = sender.width Then
                control.Visible = False
            End If
        Next
    End If
End Sub

What I know:

  1. If the form didn't have a background image, I wouldn't have the ghosting problem.
  2. If this were just a normal button I am drawing, I probably wouldn't have the ghosting problem.

What I've done, and how I've tried to fix it:

  1. Ensuring the form's doublebuffering is turned on (it was)
  2. Manually doublebuffering using the bufferedGraphics class (did not help or made it worse)
  3. Convince the designers that it doesn't need background images or the pretty glass buttons (no go)
  4. Run Invalidate() on the rectangle containing the form (did not help)
  5. Run Refresh() on the form (fixed the ghosting, but now the entire screen flashes as it reloads image)
  6. Sit in the corner of my cubicle and cry softly to myself (helped the stress, but also did not fix issue)

What I am looking for are the answers to these questions:

  1. Does anyone have any idea how to get rid of the ghosting I'm describing? Should I focus on changing the size less often? should I be focusing on buffering the background image?
  2. Are there other technologies I should be using here? Are there ActiveX controls that would be better at this than .NET user inherited ones? Is it possible to make a DirectX user-control to use the graphics card to draw itself?
  3. Is there something else I'm not thinking of here?

~~~~~~~~~~~~~~~~~~~~~~~~~ Update 1: 11/17/2009 9:21 AM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I've improved the efficiency of the draw methods by first checking to see if they need to be redrawn by checking what the new values will be vs what they already are(code changed above). This fixes some of the ghosting, however the core problem still remains to be solved.

+1  A: 

I don't see anything wrong in the code. Here is something you can try:

Draw the background image on a picturebox the size of the form, instead of the form itself. You can keep the buttons in front of it and it should look the same. Then you can control the redraw of the background image, either using the graphics object in the paint event or using a persistent image with the picture box. To prevent flickering, you can only redraw that part of the image that is within the bounds of the buttons (or the modified button and those to its right).

This isn't ideal, but it might be a usable workaround.

xpda
This may actually be a viable workaround for the issue. However, you can't tell it from the picture, but the grey background here is actually an MDI container that will contain windows launched by these buttons.The MDI Container has draw events and such as well, and I've already tried invalidating certain sections of it and redrawing portions and the like (and it was no simple task to figure out how to modify the properties on the MDIClient since it isn't an actual form control) So in a sense, I've kind of tried what you're saying to the extent I could. Thanks for the idea though!
Jrud
+3  A: 

There is a possible quick fix for your problem. Paste this code into your form:

  public partial class Form1 : Form {
    public Form1() {
      InitializeComponent();
      this.IsMdiContainer = true; 
      foreach (Control ctl in this.Controls) {
        if (ctl is MdiClient) {
          ctl.BackgroundImage = Properties.Resources.SampleImage;
          break;
        }
      }
    }
    protected override CreateParams CreateParams {
      get {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
        return cp;
      }
    }
  }

The style flag works on XP SP1 and up. It double-buffers your entire form, not just each individual control, and should eliminate the ghosting effect you see.

Hans Passant
I'm pretty sure this fixes the ghosting issue, however now I cannot see the background images past the MDI Client control. any ideas?
Jrud
I don't know what that means. Setting the MdiClient's BackgroundImage property works just fine.
Hans Passant
hmm... it isn't working fine on my PC. the grey background is showing instead of the MDIClient's BackgroundImage.
Jrud
I updated the sample code to set the MDI client window's background image. This works fine when I try it. Please modify this code so it reproduces your problem.
Hans Passant
My management staff decided that there should be a black panel behind the buttons and that they need to be spaced out and don't need to move one another so it really gets rid of the entire issue for me.I believe that your answer was the most correct and inventive solution for the issue so you get the points.The main reason that I believe I was getting a grey background is because our app actually has 4 MDI client areas on it which is really a bigger mess of code than I care to get into in this topic.Thanks for all of your help!
Jrud