2

I recently had the need to write a version of the Windows NumericUpDown control which could highlight whether a value was mandatory. It needed to do this by changing the back colour of the control. I thought this would be simple enough, but in trying to do so, I find that it has a wierd side-effect of not quite drawing all the control.

Using the code below, I am seeing this:

When I drop a control on a Windows form, and change the BackColor property (ie. to Blue), the whole of the control's number part changes colour. If, alternatively, I change my "IsMandatory" property, not quite all of the back colour changes (it leaves a border). So, if I change the BackColor to Blue, and then set IsMandatory to True, I get a LightBlue control (the mandatory colour) with a Blue border.

I cannot see why that should be, given that they both use the same code.

Ideas or explanations greatfully received.

   public partial class MyNumericUpDown : NumericUpDown
   {
      private Boolean _isMandatory = false;
      private Color _mandatoryBackColor = Color.LightBlue;
      private Color _backColor = Color.FromKnownColor(KnownColor.Window);

      [DefaultValue(typeof(Color), "Window"), Description("Overridden property")]
      override public Color BackColor
      {
         get { return _backColor; }
         set
         {
            _backColor = value;
            MyResetColors();
         }
      }

      [DefaultValue(typeof(Color), "LightBlue"), Category("Appearance")]
      public Color MandatoryBackColor
      {
         get {return _mandatoryBackColor;}
         set 
         {
            _mandatoryBackColor = value;
            MyResetColors();
         }
      }

      [DefaultValue(false), Category("Behavior")]
      public Boolean IsMandatory
      {
         get { return _isMandatory; }
         set
         {
            _isMandatory = value;
            MyResetColors();
         }
      }

      private void MyResetColors()
      {
         base.BackColor = (this.IsMandatory ? this.MandatoryBackColor : this.BackColor);
      }
   }

Here's what it looks like:

Black Light
  • 2,358
  • 5
  • 27
  • 49
  • Are you sure it is not just a repaint/update Problem? I had the same issue and when I force a repaint (bring different window to front or resize form until control in question is not visible anymore) it repainted the control with the correct colrs and no extra "border". – Xan-Kun Clark-Davis Oct 08 '15 at 11:38

3 Answers3

2

Interesting question, it demonstrates how overriding virtual members can have unexpected side-effects. The core problem is your BackColor property getter, it always returns the _backColor property value, even if you forced it to a different value with IsMandatory. That property getter is also used by Winforms when it needs to draw the control background. So you'll return Blue which explains why you see blue in your screenshot.

But oddly it still works for the text portion of the control. That's because NumericUpdown is made up of multiple controls. You've got a ContainerControl that sets the outer bounds and is the base class, you are overriding its BackColor property. But inside of it are two other controls, a TextBox that displays the text and a Control that displays the up/down buttons. Your BackColor property override does not override their BackColor properties. So the textbox portion will draw with the color you assigned to Base.BackColor

To fix this, you are going to have to stop fibbing about the BackColor. With the extra constraint that you need to make sure that this still works at design time so that the actual BackColor gets serialized and not the MandatoryColor:

[DefaultValue(typeof(Color), "Window"), Description("Overridden property")]
override public Color BackColor {
    get {
        return base.BackColor;
    }
    set {
        _backColor = value;
        MyResetColors();
    }
}

private void MyResetColors() {
    base.BackColor = this.IsMandatory && !DesignMode ? this.MandatoryBackColor : _backColor;
}
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks Hans. Yes, that makes sense re. the base control wanting to know its BackColor and it using my over-ridden property getter - I hadn't thought about that. Oddly enough (although it didn't appear in that simple test), in the 'real' version of this code it appeared that the control did not, in some cases, fill the background at all. You could see bits of text and the like between the internal textbox control and the boundry of the UpDown control. Most weird. In the end I just created an 'OptionalBackColor' property and left BackColor alone. Thanks again, Ross. – Black Light May 18 '12 at 06:51
0

The above method did not work out for me. My workaround was:

    private void smartRefresh()
    {
        if (oldBackColor != BackColor) {
            oldBackColor = BackColor;
            Hide();
            Application.DoEvents();
            Show();
            Application.DoEvents();
        }
    }

With a private member oldBackColor.

Now it always shows correctly but does not flicker.

Addendum: I think some part of the Control doesn't get painted at all (I consider it a bug) as the "mispainted" bos around it is not uniformly colored an somtimes traces of the window that was there before can be seen.

Xan-Kun Clark-Davis
  • 2,664
  • 2
  • 27
  • 38
0

Windows does not properly/completely repaint the NumericUpDown control when it is disabled.

See this post: NumericUpDown background colour change for disabled element

Enabling / disabling the control after it is displayed is a work-around.

Joe B
  • 692
  • 8
  • 18