views:

728

answers:

5

When a user clicks my Validate Button (in my C#, WinForm, .net 3.5 app) I would like to draw a border around a certain control if it is empty. Say a textbox that is named tbxLastName I thought I needed to do something like this -->

ControlPaint.DrawBorder(Graphics.FromHwnd(this.Handle), 
    tbxLastName.ClientRectangle, Color.Firebrick, ButtonBorderStyle.Solid);

Unfortunately, I have no idea what to put for the Graphics Object as what I have does NOTHING.

All of the examples I have come across, MSDN - HERE, have this code in a Paint Event. Like so -->

private void panel1_Paint(object sender, PaintEventArgs e)
{    
    ControlPaint.DrawBorder(e.Graphics, this.panel1.ClientRectangle, 
        Color.DarkBlue, ButtonBorderStyle.Solid);
}

I, however, only want to have the border appear while certain conditions are meet which is kicked off by a Button_Click


So many of the suggestions suggest using a container object to hold the textbox and call it's Paint_Event. I did this and a box appears but NOT around the control. It appears in the Top Left corner of the Container Control. Here is what I am doing -->

    private void grpImmunizationCntrl_Paint(object sender, PaintEventArgs e)
    {
        if (lkuNOImmunizationReason.Text.Equals(string.Empty)
        {
           ControlPaint.DrawBorder(
                    e.Graphics, lkuNOImmunizationReason.ClientRectangle,
                        Color.Firebrick, ButtonBorderStyle.Solid);
        }
    }

EDIT

This is what I came up with combining suggestions here with what worked for me.

    public static void HighlightRequiredFields(Control container, Graphics graphics, Boolean isVisible)
    {
        Rectangle rect = default(Rectangle);
        foreach (Control control in container.Controls)
        {
            if (control.Tag is string && control.Tag.ToString() == "required")
            {
                rect = control.Bounds;
                rect.Inflate(3, 3);
                if (isVisible && control.Text.Equals(string.Empty))
                {
                    ControlPaint.DrawBorder(graphics, rect, Color.FromArgb(173,216,230), ButtonBorderStyle.Solid);
                }
                else
                {
                    ControlPaint.DrawBorder(graphics, rect, container.BackColor, ButtonBorderStyle.None);
                }
            }

            if (control.HasChildren)
            {
                foreach (Control ctrl in control.Controls)
                {
                    HighlightRequiredFields(ctrl, graphics, isVisible);
                }
            }
        }
    }

I call this from the Paint_Event of any Container I need to.

+1  A: 

Hi,

Textboxes do not call the OnPaint method (See Note section of this page). A way round this, is to place the textbox in a panel, which is slightly bigger. Then, change the background colour whenever the button is clicked. A thread over at MSDN forums has a few solutions.

Edit To clarify the panel solution, simply create a panel and add your textbox to it: e.g.

private void MyForm_Load(object sender, EventArgs e)
{
     myPanel.Controls.Add(tbxLastName); //Make sure the panel size is slightly bigger than the text box (so that it looks like a border)
}

Then, handle your button click event:

private void myButton_Click(object sender, EventArgs e)
    {
        if (tbxLastName.Text == "")
        {
            myPanel.BackColor = Color.Red;
        }
        else
        {
            myPanel.BackColor = Color.Transparent;
        }
    }
keyboardP
How about if it is a Control that has an OnPaint method? Also, any ideas on why, when I placed it in a groupbox that it was missing the control when it drew the Rect(see Edit at bottom of OP)? Thanks
Refracted Paladin
Unless there's a reason you want to use the OnPaint method, you could try the method in my updated answer.
keyboardP
Oh, I see what you are doing now. I will have to think about this. I have between 30 and 40 controls that MIGHT get a Box drawn around them. I am wondering how I would implement this on that scale.
Refracted Paladin
Ah, I see. I thought it was just a few controls. If you set a breakpoint, just after `ControlPaint.DrawBorder( e.Graphics, lkuNOImmunizationReason.ClientRectangle...`, what value are you getting for the ClientRectangle property?
keyboardP
+2  A: 

you can use a list of actions field in the form and add or remove, custom drawings:

// field
List<Action<Graphics>> drawings = new List<Action<Graphics>>();

// on click event:
drawings.Add(delegate(Graphics g) {
    var rect = tbxLastName.Bounds;
    rect.Inflate(1, 1); // make rectange a bit larger than textbox
    ControlPaint.DrawBorder(g, rect, 
    Color.DarkBlue, ButtonBorderStyle.Solid);
});
// make sure you added only once or clear before
panel1.Refresh(); // refresh panel to force painting


// Paint method:
foreach (var draw in drawings) {
    draw(e.Graphics);
}

This way you can add more than one border

Alex LE
+1 for pointing out the .Inflate method, I never noticed that one before, very handy!
Shawn Steward
A: 
    protected override void OnTextChanged(EventArgs e)
    {
        base.OnTextChanged(e);
        if (string.IsNullOrEmpty(Text))
        {
            this.BorderStyle = BorderStyle.FixedSingle;
        }
        else 
        {
            this.BorderStyle = BorderStyle.Fixed3D;
        }
    }
serhio
+1  A: 

The reason that your rectangle is "missing" the text box is that ClientRectangle only contains the size of the control, not the location. Try this instead:

private void grpImmunizationCntrl_Paint(object sender, PaintEventArgs e)
{
    if (lkuNOImmunizationReason.Text.Equals(string.Empty)
    {
       ControlPaint.DrawBorder(
                e.Graphics, new Rectangle(lkuNOImmunizationReason.Left, lkuNOImmunizationReason.Top, lkuNOImmunizationReason.ClientRectangle.Width, lkuNOImmunizationReason.ClientRectangle.Height),
                    Color.Firebrick, ButtonBorderStyle.Solid);
    }
}
TJMonk15
+1  A: 

I just did something similar with VB.Net, with help from this thread. I have a Panel container around each set of my controls, and in the OnPaint handler for the Panel, I loop through all the children controls in the Panel and draw borders around them if they have the tag="required". I only display the borders on Edit or New so I created the fVisible parameter to toggle these on and off. When I want to fire this off, I call Panel.Refresh() so it fires the OnPaint event. This way all I have to do is set the tags on the required controls at design time, and add a handler for the container Panels, and it all works dynamically.

Here's the shared function I'm calling from all of my Panel's OnPaint Event Handlers. (Sorry I know you're using C# and this is VB but it's pretty basic.)

Friend Sub HighlightRequiredFields(ByVal pnlContainer As Panel, ByVal gr As Graphics, ByVal fVisible As Boolean)
    Dim rect As Rectangle
    For Each oControl As Control In pnlContainer.Controls
        If TypeOf oControl.Tag Is String AndAlso oControl.Tag.ToString = "required" Then
            rect = oControl.Bounds
            rect.Inflate(1, 1)
            If fVisible Then
                ControlPaint.DrawBorder(gr, rect, Color.Red, ButtonBorderStyle.Solid)
            Else
                ControlPaint.DrawBorder(gr, rect, pnlContainer.BackColor, ButtonBorderStyle.None)
            End If
        End If
        If TypeOf oControl Is Panel Then HighlightRequiredFields(DirectCast(oControl, Panel), gr, fVisible)
    Next
End Sub
Shawn Steward
Interesting way to do it, thanks. I think I will combine your way with what I posted below as I need the highlight to go away if text is entered. We, unfortunately, have a very unusual model in that they do not click Edit or New first but AFTER they have entered the data. Weird, I know!
Refracted Paladin
Haha that definitely is werid! Glad to help.
Shawn Steward