views:

246

answers:

3

I have a winform app that we use in house. It has many individual controls on each of it's 25 "pages"(usercontrols). Our user base prefers very technicolor apps...they want a TextBox to be outlined Blue if it is a required field (color should go away if data entered). They want a TextBox to change to outlined Green if data has been changed in it to remind them to save. They want a currently highlighted TextBox to be outlined RedOrange.

I have been trying to come at this from many different angles (some of you have probably seen similar posts by me lately). Non of them work... So one way I KNOW will work is to register the paint event for every control and check for a "required" tag for the required portion. The OnFocus for the current field portion and finally the Validate event for the data changed portion.

I know this is not the best way or at least I STRONGLY suspect it isn't but I am out of time, nearly, and nearing my point of frustration. That being said, will that DESTROY my app's responsiveness? Is there a better way? Can I override the base control to color on different premises so that I don't have to go to each of the 100+ controls?

Any idea would be welcome because I am between my stupid Paint_Event idea and rewriting all the controls in WPF... :)

I will be rewarding a solution that works for me and that I can implement shortly with a Bounty.

I am so sick of colors...


Here is my attempt based on suggestions.

public class MyTextBox : TextBox
{
    private bool _isRequired;
    public bool isRequired
    {
        get
        {
            return _isRequired;
        }
        set
        {
            _isRequired = value;
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        if (isRequired && base.Text.Equals(string.Empty))
        {
            HighlightControl(e.Graphics);
        }
    }

    private void HighlightControl(Graphics graphics)
    {
        ControlPaint.DrawBorder(
                graphics,
                this.ClientRectangle,
                Properties.Settings.Default.RequiredFieldColor,
                Properties.Settings.Default.BorderWidth,
                Properties.Settings.Default.BorderStyle,
                Properties.Settings.Default.RequiredFieldColor,
                Properties.Settings.Default.BorderWidth,
                Properties.Settings.Default.BorderStyle,
                Properties.Settings.Default.RequiredFieldColor,
                Properties.Settings.Default.BorderWidth,
                Properties.Settings.Default.BorderStyle,
                Properties.Settings.Default.RequiredFieldColor,
                Properties.Settings.Default.BorderWidth,
                Properties.Settings.Default.BorderStyle);
    }
}
+3  A: 

You can inherit from base control classes and add your own drawing logic. That won't be very expensive performance-wise, but, if your app is of significant size, you'll have to replace each occurence of standard TextBox with your own implementation.

Anton Gogolev
+1: Yes, don't add a handler to every textbox's paint event when you can do it once in one textbox subclass and put it everywhere it needs to be.
Greg D
+4  A: 

I don't know the particulars of your app, but you could derive your own control from the base TextBox and let it handle much of this for you. Some thoughts:

  • Give it a bool Required property and some internal logic to color accordingly.

  • Have the textbox respond to its own events - when text is entered, it can do the right thing - change colors or whatever is appropriate.

  • Provide your derived control with properties to set the colors that get used for each condition, then you can switch them easily when the users decide they want pink rather than green.

  • You can utilize the focus events to "know" whether your TextBox (this is the one control you mentioned, so I'll assume this is the main control used here) has focus or lost it and the text change events can be used to drive all the color changes to the control.

  • You can certainly wire up all the text boxes to control the Apply/OK/whatever buttons to determine if the buttons should be enabled, assuming you have an Apply button or something like that which stores the data on click. There are a number of ways to communicate this. It's easy enough to iterate through the controls and ask their state.

Seems like this would work just fine.
What have you tried? You didn't really mention what you'd tried that didn't work.

itsmatt
See this post for 1 way I tried --> http://stackoverflow.com/questions/2193549/draw-border-around-a-control-on-button-click
Refracted Paladin
Thanks, I will look google around on how I start something like this. Great answer and it sounds like it might be what I want.
Refracted Paladin
+3  A: 

You've got a problem, TextBox is, erm, special. Windows Forms leaves all painting to the native Windows EDITBOX control, the Paint event won't be raised. That can be fixed by setting the UserPaint control style to true:

using System;
using System.Drawing;
using System.Windows.Forms;

class MyTextBox : TextBox {
  public MyTextBox() {
    this.SetStyle(ControlStyles.UserPaint, true);
  }
  protected override void OnPaint(PaintEventArgs e) {
    base.OnPaint(e);
    // Paint something
    //...
  }
}

Copy and paste this into a new class, compile and drop the new control from the top of the toolbox. Try it out, you'll be quite disappointed. What you see is the result of close to 20 years of appcompat hacks on a control that dates back to Windows version 2. One of the grave crimes that EDITBOX commits is painting itself without generating a WM_PAINT message. That was important way back when Windows had to run on a 386SUX, keeping it compatible with old programs prevented Microsoft from fixing its behavior.

No happy answers here, you'll have to give up on the idea of customizing the border. What you can do is give the control a distinct BackColor when it has the focus. For example:

class MyTextBox : TextBox {
  Color mBackColor;
  protected override void OnEnter(EventArgs e) {
    mBackColor = base.BackColor;
    base.BackColor = Color.AliceBlue;
    base.OnEnter(e);
  }
  protected override void OnLeave(EventArgs e) {
    base.BackColor = mBackColor;
    base.OnLeave(e);
  }
}
Hans Passant
Hmm, normally I would never even THINK to question someone with the Rep and Respect you have but I already had tried this. See my edit in my original question. It works...drew a border around my TextBox and when I click Validate which does a `this.Refresh();` on the UserControl hosting the TextBox the border went away if there was text in it....The only thing it doesn't do is go away instantly.
Refracted Paladin
Your code does not work when I try it, OnPaint never runs. That's expected, I have no idea why it would work for you. I'm running Win7 with visual styles enabled.
Hans Passant
I am also running Win7 with visual styles... weird. I only have what is shown here.
Refracted Paladin
I am an idiot...I'll explain later as I have to gather my thoughts on this. Needless to say you are correct.
Refracted Paladin