Pergunta

What I have is a Windows form, in C#, with 7 text boxes. Each text box updates 2 or 3 others when its value is changed and accepted. What I want to do is somehow take those text boxes that need to be updated and make them "flash" with a light back color or something. The purpose is to show the user what is being updated with an added bit of flair.

I'm not sure if there is an easy way to do this and that is why I'm asking here. I can use a timer, a while loop, and a back color with a decreasing alpha channel on the text box control back color, I think, but I want to see if there is a better way.

jQuery UI has a "Highlight" effect that shows what I want to accomplish (although I want mine to be a bit slower). Just go here to the jQuery UI Effects Demo page, select "highlight" from the drop-down box in the window, and click "Run Effect".

Edit
I had to go with my own solution based on my time and resource constraints, but text boxes do not support transparent colors as mentioned by Hans Passant. So, I used a self-stopping timer that increases the R, G, and B values until the control is completely white (R=255, G=255, B=255);

Edit 2
Wound up recoding the flash event as an extension method using a variation of George Johnston's solution after we updated to .NET 4.0. This is a much cleaner solution, I feel, and the extension method makes it automatically available to anyone using it.

Foi útil?

Solução

You could spin off a seperate thread per flashing textbox as to not block your form from being used during the flashing of your textbox(s). Be sure to invoke your form as spinning of the thread will require cross threading. Full solution below.

private void Form1_Load(object sender, EventArgs e)
{
    // textBox1 is the control on your form.
    // 1000 is the total interval between flashes
    // Color.LightBlue is the flash color
    // 10 is the number of flashes before the thread quits.
    Flash(textBox1, 1000,Color.LightBlue,10);
    Flash(textBox2, 1500,Color.Green,10);
    Flash(textBox3, 100,Color.Red,10);
    Flash(textBox4, 500,Color.Brown,10);
    Flash(textBox5, 200,Color.Pink,10);
}

public void Flash(TextBox textBox, int interval, Color color, int flashes)
{
    new Thread(() => FlashInternal(textBox, interval, color, flashes)).Start();
}

private delegate void UpdateTextboxDelegate(TextBox textBox, Color originalColor);
public void UpdateTextbox(TextBox textBox, Color color)
{
    if (textBox.InvokeRequired)
    {
        this.Invoke(new UpdateTextboxDelegate(UpdateTextbox), new object[] { textBox, color });
    }
    textBox.BackColor = color;
}

private void FlashInternal(TextBox textBox, int interval, Color flashColor, int flashes)
{
    Color original = textBox.BackColor;
    for (int i = 0; i < flashes; i++)
    {

        UpdateTextbox(textBox, flashColor);
        Thread.Sleep(interval/2);
        UpdateTextbox(textBox, original);
        Thread.Sleep(interval/2);
    }
}

This avoids having to put supporting timer controls on your form.

Outras dicas

The WPF would seem perfect for this. You can build it in WPF and use it in WinForms as a HostedElement. Add new project WPF User Control, and this in xaml:

<UserControl x:Class="WpfControlLibrary1.UserControl1"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="20" d:DesignWidth="300">
<TextBox>
    <TextBox.Triggers>
        <EventTrigger RoutedEvent="TextBox.TextChanged">
            <BeginStoryboard>
                <Storyboard AutoReverse="False" BeginTime="0" >
                    <DoubleAnimation Storyboard.TargetName="Foo"
                                 Storyboard.TargetProperty="Opacity"
                                 From="0" To="1" Duration="0:0:1"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </TextBox.Triggers>
    <TextBox.Background>
        <SolidColorBrush Opacity="1" x:Name="Foo" Color="LightGray" />
    </TextBox.Background>
</TextBox>
</UserControl>

(it could do a little work, but it's a start). There you have it - a fancy textbox :)

Build the solution, and a new item will appear in Toolbox - just drag & drop to your Form, the WPF will be hosted inside ElementHost element. The beauty of it is that you can do much much more in visual styles in WPF, however, it's hosted WPF which adds certain weight to your solution...

Derive your own class from TextBox. Give it a Flash() method that starts the flashing. Just change the BackColor to a pastel color. Don't use alpha, that doesn't work on a TextBox.

You ought to have all instances of this class share a common Timer so they will flash at the same time. Make the timer static and reference-count the number of instances you have. Add up in the constructor, down in the Dispose(bool) override.

If you are set on your ways in using WinForms, then with my limited knowledge, I can suggest getting third party controls to help out. A few to name are Telerik and ComponentOne. If you want something WinForms like, you can probably utilize WPF, and develop custom animations in XAML (which I think are similar to Silverlight XAML in creating UI states and animations). Other than these, I'm out of experience to provide any help.

Depending on your app, one flashy way of doing this is by changing the gamma of an image of your textbox. This of course depends on how much time you want to put into this, but it's certainly doable. I've seen several tutorials on how to adjust the gamma of an image and getting an image of your control is trivial.

That said, I also believe it's nontrivial to set the backcolor of the textbox to transparent. From your wording I can only guess you want to fade the color from the underlying control's backcolor to the textbox' backcolor, in which case the problem is trivial again. But if you have i.e. a background image, you should perhaps reconsider. Nonetheless, it's still possible and I can dig up a link for you on how to do this if that's what you're trying to accomplish.

The quick'n easy solution would be to animate the text color and the back color from white to your current foreground color.

If you're not interested in using threading, building on George Johnston's answer here my implementation is as follows:

private bool CurrentlyFlashing = false;
private void FlashInternal(TextBox textBox, int interval, Color flashColor, int flashes)
{
    if (CurrentlyFlashing) return;

    CurrentlyFlashing = true;
    Color original = textBox.BackColor;
    for (int i = 0; i < flashes; i++)
    {
        UpdateTextbox(textBox, flashColor);
        Application.DoEvents();
        Thread.Sleep(interval / 2);
        UpdateTextbox(textBox, original);
        Application.DoEvents();
        Thread.Sleep(interval / 2);
    }
    CurrentlyFlashing = false;
}
private delegate void UpdateTextboxDelegate(TextBox textBox, Color originalColor);
public void UpdateTextbox(TextBox textBox, Color color)
{
    if (textBox.InvokeRequired)
    {
        this.Invoke(new UpdateTextboxDelegate(UpdateTextbox), new object[] { textBox, color });
    }
    textBox.BackColor = color;
}

Pretty old question, but I thought i would update with a more modern answer, since async/await did not exist in 2010 when the last answers answered

I also updating it to uses a nicely fading single-flash rather than a seizure-inducing on-off blink, and and changed the typing so that it is not restricted to be used on Textboxes.

Since it uses async/await, it is safe to do on the main thread without needing the delegate or the InvokeRequired check. Even if you set the duration of the fade to something long, you will still be able to interact with the application while the fade loop is running.

    private bool CurrentlyFlashing = false;
    private async void FlashControl(Control control)
    {
        Color flashColor = Color.Yellow;

        float duration = 500; // milliseconds
        int steps = 10;
        float interval = duration / steps;

        if (CurrentlyFlashing) return;
        CurrentlyFlashing = true;
        Color original = control.BackColor;

        float interpolant = 0.0f;
        while (interpolant < 1.0f)
        {
            Color c = InterpolateColour(flashColor, original, interpolant);
            control.BackColor = c;
            await Task.Delay((int) interval);
            interpolant += (1.0f / steps);
        }

        control.BackColor = original;

        CurrentlyFlashing = false;
    }

    public static Color InterpolateColour(Color c1, Color c2, float alpha)
    {
        float oneMinusAlpha = 1.0f - alpha;
        float a = oneMinusAlpha * (float)c1.A + alpha * (float)c2.A;
        float r = oneMinusAlpha * (float)c1.R + alpha * (float)c2.R;
        float g = oneMinusAlpha * (float)c1.G + alpha * (float)c2.G;
        float b = oneMinusAlpha * (float)c1.B + alpha * (float)c2.B;
        return Color.FromArgb((int)a, (int)r, (int)g, (int)b);
    }

I hard-coded the colour and timing rather than exposing them as function parameters as other answerers did, since I generally want them to be the same across my application if I reuse this.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top