Krypton-Toolkit-Suite-Extended-NET-5.470 icon indicating copy to clipboard operation
Krypton-Toolkit-Suite-Extended-NET-5.470 copied to clipboard

Status Strip Feature Request

Open fdncred opened this issue 6 years ago • 19 comments

Please add the ability to change the color of each individual item in the status strip, to include foreground color, back ground color as well as StartGradient and EndGradient colors.

fdncred avatar Jun 01 '18 13:06 fdncred

A version is now available, just need to implement the text colour.

Wagnerp avatar Jun 01 '18 18:06 Wagnerp

Quick turn @Wagnerp, Thanks!. Could you enable changing the text color aka ForeColor? Ideally it would work like this.

If (ForeColor != Color.Empty)
    draw the text in the color specified with the configured cleartext/antialiased properties.

If (BackColor != Color.Empty)
    draw the background using a solid brush.
else If (GradiantColorOne != Color.Empty || GradientColorTwo != Color.Empty)
   draw the background using the LinearGradientBrush()

At that point, one can control just about all the attributes of the status strip.

fdncred avatar Jun 01 '18 19:06 fdncred

I think it's done, is this what you mean?

protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            Rectangle r = new Rectangle(0, 0, Width, Height);

            if (ForeColor != Color.Empty)
            {
                g.TextRenderingHint = TextRenderingHint.AntiAlias;

                Font typeface = new Font(Font.FontFamily, Font.Size, Font.Style, Font.Unit);

                SolidBrush brush = new SolidBrush(ForeColor);

                g.DrawString(Text, typeface, brush, 0, 0);
            }
            else if (BackColor != Color.Empty)
            {
                using (SolidBrush sb = new SolidBrush(BackColor))
                {
                    g.FillRectangle(sb, r);
                }
            }
            else if (GradientColourOne != Color.Empty || GradientColourTwo != Color.Empty)
            {
                using (LinearGradientBrush lgb = new LinearGradientBrush(r, GradientColourOne, GradientColourTwo, GradientMode))
                {
                    g.FillRectangle(lgb, r);
                }
            }

            base.OnPaint(e);
        }

Wagnerp avatar Jun 02 '18 06:06 Wagnerp

Been on vacation. Back now. I was thinking something more like this. The main difference is that I changed your first "else if" into just an "if". The only "else if" should occur regarding the background, not the foreground.

protected override void OnPaint(PaintEventArgs e)
{
    Graphics g = e.Graphics;

    Rectangle r = new Rectangle(0, 0, Width, Height);

    if (ForeColor != Color.Empty)
    {
        g.TextRenderingHint = TextRenderingHint.AntiAlias;

        Font typeface = new Font(Font.FontFamily, Font.Size, Font.Style, Font.Unit);

        SolidBrush brush = new SolidBrush(ForeColor);

        g.DrawString(Text, typeface, brush, 0, 0);
    }
    
    if (BackColor != Color.Empty)
    {
        using (SolidBrush sb = new SolidBrush(BackColor))
        {
            g.FillRectangle(sb, r);
        }
    }
    else if (GradientColourOne != Color.Empty || GradientColourTwo != Color.Empty)
    {
        using (LinearGradientBrush lgb = new LinearGradientBrush(r, GradientColourOne, GradientColourTwo, GradientMode))
        {
            g.FillRectangle(lgb, r);
        }
    }

    base.OnPaint(e);
}

The only other thing that's a bit wiggy is that you're hard coding the TextRenderingHint. Should that be extracted from the system? I believe if one is using ClearText it's not just AntiAlias, but I could be wrong. I'm not real familiar with TextRenderingHint.

You may also want to use a using statement in the first if as well for the brush.

One last thing - This doesn't quite work. It appears to draw the text foreground and background color in the color you specify and the write over it with the palette color. I'm not sure what's going on exactly. Perhaps the base.OnPaint() is causing the second draw.

fdncred avatar Jun 11 '18 13:06 fdncred

I'll update the code. There is one bug, which I don't know exactly where it's coming from. The bug is creating a text ghosting effect. Will try your solution though.

Wagnerp avatar Jun 11 '18 15:06 Wagnerp

I saw that too and noticed that the text wasn't aligned exactly the same way as the default renderer draws it. Thanks!

fdncred avatar Jun 11 '18 15:06 fdncred

I think the base.OnPaint() call was the issue. Thanks for your suggestion.

Wagnerp avatar Jun 11 '18 16:06 Wagnerp

Are you saying you got it to work? Because commenting that out only enabled the ForeColor - not the BackColor (assuming you're not trying to use the Gradient). Also, Did you figure out how to align the text because it was a little off on the left side when I tried it.

In the attached image the text on the Gradient background is supposed to be Red and the Third status items shows misaligned text when you have the OnPaint enabled (not commented out). playground_2018-06-11_11-45-52

fdncred avatar Jun 11 '18 16:06 fdncred

I think so. Don't know about the alignment though. Might look into a possible TextGlow colour property to make the text stand out more.

Wagnerp avatar Jun 11 '18 16:06 Wagnerp

Just downloaded the latest code base. FG works BG doesn't work.

var e = new ExtendedControls.ExtendedToolkit.ToolstripControls.ExtendedToolStripStatusLabel();
e.Text = "This is a test of the emergency blah blah";
e.ForeColor = Color.Red;
e.BackColor = Color.Black;
statusStrip1.Items.Add(e);

I put this code in the public Form1() method of your playground app to test it.

I then changed the code to this:

var e = new ExtendedControls.ExtendedToolkit.ToolstripControls.ExtendedToolStripStatusLabel();
e.Text = "This is a test of the emergency blah blah";
e.ForeColor = Color.Red;
//e.BackColor = Color.Black;
e.GradientColourOne = Color.Black;
e.GradientColourTwo = Color.DarkGray;
e.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
statusStrip1.Items.Add(e);

And the text doesn't show up at all

fdncred avatar Jun 13 '18 16:06 fdncred

I think I have it mostly working now. The problem is the background was drawing over the foreground. So I moved the DrawString last in the OnPaint() method.

        protected override void OnPaint(PaintEventArgs e)
        {
            // Set a graphics variable
            Graphics g = e.Graphics;

            // Rectangle variable
            Rectangle r = new Rectangle(0, 0, Width, Height);

            if (BackColor != Color.Empty)
            {
                using (SolidBrush sb = new SolidBrush(BackColor))
                {
                    g.FillRectangle(sb, r);
                }
            }
            else if (GradientColourOne != Color.Empty || GradientColourTwo != Color.Empty)
            {
                using (LinearGradientBrush lgb = new LinearGradientBrush(r, GradientColourOne, GradientColourTwo, GradientMode))
                {
                    g.FillRectangle(lgb, r);
                }
            }

            if (ForeColor != Color.Empty)
            {
                g.TextRenderingHint = TextRenderingHint.AntiAlias | TextRenderingHint.ClearTypeGridFit;

                Font typeface = new Font(Font.FontFamily, Font.Size, Font.Style, Font.Unit);

                SolidBrush brush = new SolidBrush(ForeColor);

                g.DrawString(Text, typeface, brush, 0, 0);
            }

            //base.OnPaint(e);
        }

On my system, adding ClearyTypeGridFit also helps render the text closer to what the OS does.

And I'm not sure what TextGlow is supposed to do since there is nothing regarding it in the OnPaint and it's not calling the base. I figure Glow isn't doing anything at this point.

I also had to change how BackColor is being used

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        //public override Color BackColor { get => new Color(); set {; } }
        public Color BackColor { get; set; }

fdncred avatar Jun 13 '18 17:06 fdncred

Now that the background is able to be changed one could do something like this to alert a user. This also works with Gradients.

ss

fdncred avatar Jun 13 '18 18:06 fdncred

I've added the code that you have suggested, and it does look better. I've added new code for an alert option, but can't understand why it doesn't work. The TextGlow feature doesn't currently work at the moment, as I'm still working on a C# implementation, because most of the examples I've come across are written in VisualBasic.

Wagnerp avatar Jun 14 '18 10:06 Wagnerp

Regarding the Alert. I brought your code over and debugged it. The problem was you were setting the wrong variables and not forcing a redraw. Try this.

public async void BlinkLabel()
{
    while (true)
    {
        await Task.Delay(_flashInterval);
        BackColor = BackColor == AlertColourOne ? AlertColourTwo : AlertColourOne;
        ForeColor = AlertTextColour;
        // Force a redraw
        Invalidate();
    }
}

The same is true for SoftBlink()

public async void SoftBlink(Color alertColour1, Color alertColour2, Color alertTextColour, short cycleInterval, bool bkClr)
{
    var sw = new Stopwatch();
    sw.Start();
    short halfCycle = (short)Math.Round(cycleInterval * 0.5);

    while (true)
    {
        await Task.Delay(1);
        var n = sw.ElapsedMilliseconds % cycleInterval;
        var per = (double)Math.Abs(n - halfCycle) / halfCycle;
        var red = (short)Math.Round((alertColour2.R - alertColour1.R) * per) + alertColour1.R;
        var grn = (short)Math.Round((alertColour2.G - alertColour1.G) * per) + alertColour1.G;
        var blw = (short)Math.Round((alertColour2.B - alertColour1.B) * per) + alertColour1.B;
        var clr = Color.FromArgb(red, grn, blw);

        if (bkClr)
        {
            BackColor = clr;
        }
        else
        {
            //ctrl.ForeColor = clr;
            //ForeColor = alertTextColour;
            ForeColor = clr;
        }

        // Force a redraw
        Invalidate();
    }
}

Also, I noticed that your property defaults aren't getting set when using Code First versus using the designer. So, I added the defaults to the constructor as well, otherwise the AlertBlinkInterval was set to 0 no matter what I set it to in my code.

public ExtendedToolStripStatusLabel()
{
    Alert = false;
    AlertColourOne = Color.White;
    AlertColourTwo = Color.Black;
    AlertTextColour = Color.Red;
    GradientColourOne = Color.Empty;
    GradientColourTwo = Color.Empty;
    TextGlow = Color.White;
    GradientMode = LinearGradientMode.ForwardDiagonal;
    TextGlowSpread = 5;
    AlertBlinkInterval = 256;
}

The only other thing I'd add to your Alert functionality is duration. So you can set the alert and tell it to "blink" for 5 seconds (or some duration) and then go back to normal.

fdncred avatar Jun 14 '18 14:06 fdncred

Here's my changes with blinkDuration. I set the default duration to 10 seconds arbitrarily. Maybe there should be a property for that?

public async void BlinkLabel(long blinkDuration = 10_000)
{
    var sw = Stopwatch.StartNew();
    var fg = ForeColor;
    var bg = BackColor;

    while (sw.ElapsedMilliseconds < blinkDuration)
    {
        await Task.Delay(_flashInterval);
        BackColor = BackColor == AlertColourOne ? AlertColourTwo : AlertColourOne;
        ForeColor = AlertTextColour;
        // Force a redraw
        Invalidate();
    }

    BackColor = bg;
    ForeColor = fg;
    Invalidate();
    sw.Stop();
}

public async void SoftBlink(Color alertColour1, Color alertColour2, Color alertTextColour, short cycleInterval, bool bkClr, long blinkDuration = 10_000)
{
    var sw = Stopwatch.StartNew();
    short halfCycle = (short)Math.Round(cycleInterval * 0.5);
    var fg = ForeColor;
    var bg = BackColor;

    while (sw.ElapsedMilliseconds < blinkDuration)
    {
        await Task.Delay(1);
        var n = sw.ElapsedMilliseconds % cycleInterval;
        var per = (double)Math.Abs(n - halfCycle) / halfCycle;
        var red = (short)Math.Round((alertColour2.R - alertColour1.R) * per) + alertColour1.R;
        var grn = (short)Math.Round((alertColour2.G - alertColour1.G) * per) + alertColour1.G;
        var blw = (short)Math.Round((alertColour2.B - alertColour1.B) * per) + alertColour1.B;
        var clr = Color.FromArgb(red, grn, blw);

        if (bkClr)
            BackColor = clr;
        else
            ForeColor = clr;

        // Force a redraw
        Invalidate();
    }

    BackColor = bg;
    ForeColor = fg;
    Invalidate();
    sw.Stop();
}

fdncred avatar Jun 14 '18 14:06 fdncred

I've uploaded new code with a definable BlinkDuration property. Thanks for your bug testing, it's much appreciated.

Wagnerp avatar Jun 14 '18 15:06 Wagnerp

You are welcome.

Don't these lines need to default to the property BlinkDuration? https://github.com/Wagnerp/Krypton-Toolkit-Suite-Extended-NET-4.70/blob/75200438afff2779d4e08fab47923942612b5cae/Source/Krypton%20Toolkit%20Suite%20Extended/Extended%20Controls/ExtendedToolkit/ToolstripControls/ExtendedToolStripStatusLabel.cs#L423 like

public async void BlinkLabel(long blinkDuration = BlinkDuration) 

https://github.com/Wagnerp/Krypton-Toolkit-Suite-Extended-NET-4.70/blob/75200438afff2779d4e08fab47923942612b5cae/Source/Krypton%20Toolkit%20Suite%20Extended/Extended%20Controls/ExtendedToolkit/ToolstripControls/ExtendedToolStripStatusLabel.cs#L451

public async void SoftBlink(Color alertColour1, Color alertColour2, Color alertTextColour, short cycleInterval, bool bkClr, long blinkDuration = BlinkDuration) 

Otherwise the BlinkDuration property does nothing since it's not used anywhere. Or am I missing something?

fdncred avatar Jun 14 '18 15:06 fdncred

I'll put it in. I don't know if these methods need to be put into the OnPaint method, as it's not doing anything when called, if so I can create an enum + property for the user to choose from.

Wagnerp avatar Jun 14 '18 15:06 Wagnerp

Note to myself: Revisit at some point.

Wagnerp avatar Sep 29 '18 09:09 Wagnerp