将定时器添加到自定义控件以制作闪烁的光标

Add Timer to Custom Control to make a Blinking Cursor

不知何故,我设法让自己再次处于 Google 或我的问题措辞都没有给我解决方案的地方。

所以,这么说吧,我正在从头开始创建一个 TextBox,派生自 System.Windows.Forms.Control。到目前为止,我已经能够绘制 Text 变量,并且还为其添加了编辑功能(就像正常的 TextBox 通常那样)。

这个可怕的耗时练习的想法是创建我自己的自定义控件库,这些控件具有最大的主题功能以用于我开发的软件。

那么,我的问题是:TextBox 中绘制闪烁的光标。

我可以绘制一条静态线,它会根据 CursorPosition 所处的位置从左向右移动。通过 System.Timers.Timer 向控件(因此 if(blinker))添加了一个计时器,还尝试了 System.Windows.Forms.Timer,但由于不允许我在添加控件时设置任何属性而弄乱了我的设计器到一个表格,但仍然没有启动我的绘图事件。我确实在 Tick 事件中放置了 Invalidate(_Cursor) 以确保仅重绘 Cursor 位置,甚至在没有参数的情况下尝试但仍然无济于事。

protected override void OnPaint(PaintEventArgs e)
{
    ...
    if (Focused)
    {
        if (blinker)
            e.Graphics.FillRectangle(new SolidBrush(_CursorColor), _Cursor);
        foreach (Rectangle border in Borders)
        {
            if(_DrawBorders[Borders.IndexOf(border)])
                e.Graphics.FillRectangle(new SolidBrush(BorderColor), border);
        }
    }
    ...
}

我也设置了

DoubleBuffered = true;
SetStyle(ControlStyles.UserPaint | ControlStyles.Selectable | ControlStyles.ResizeRedraw | 
ControlStyles.OptimizedDoubleBuffer | ControlStyles.StandardClick | ControlStyles.AllPaintingInWmPaint, true);

这有助于解决我遇到的可怕的闪烁和输入问题。

EDIT **(REMOVED AS IT IS FIXED USING COMMENT BY Hans Passant's SUGGESTION )**

好的,现在计时器开始工作了,继续我上面提到的问题。绘制光标。

    private void _CursorBlinkTime_Tick(object sender, EventArgs e)
    {
        blinker = !blinker;
        Console.WriteLine("Blink!");
        Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        Height = TextRenderer.MeasureText("A", Font).Height + (2 * BorderWidth) + (2 * TextPadding);
        CursorPosition = (BorderWidth + TextPadding) + (_SelectedStart * (TextRenderer.MeasureText("_", Font).Width));
        _InputBounds = new Rectangle(BorderWidth + TextPadding, BorderWidth + TextPadding, Width - (2 * BorderWidth) - (2 * TextPadding), Height - (2 * BorderWidth));
        base.OnPaint(e);
        e.Graphics.Clear(BackColor);

        e.Graphics.DrawString(Text, Font, new SolidBrush(TextColor), _InputBounds.Location);
        foreach (Rectangle border in Borders)
        {
            if (_DrawBorders[Borders.IndexOf(border)])
                e.Graphics.FillRectangle(new SolidBrush(InactiveBorderColor), border);
        }

        if (Focused)
        {
            // This is where the Cursor is drawn, right before the borders.
            // I've tried to move it to after the border is drawn but same result, nothing is drawn.
            // I've ran through the entire OnPaint using Step-by-Step and while it does
            // fire the draw event, nothing is drawn.
            // BackColor = FromArgb(40,40,40), _CursorColor = Color.Red
            // _Cursor is a Rectangle that reads at this moment:
            // Rectangle(5,0,2,21) Which given all other variables should show result?
            if (blinker)
                e.Graphics.FillRectangle(new SolidBrush(_CursorColor), _Cursor);
            foreach (Rectangle border in Borders)
            {
                if(_DrawBorders[Borders.IndexOf(border)])
                    e.Graphics.FillRectangle(new SolidBrush(BorderColor), border);
            }
        }
        else if (!Focused)
        {
            if(string.IsNullOrWhiteSpace(Text))
                e.Graphics.DrawString(WaterMarkText, Font, new SolidBrush(WaterMarkColor), _InputBounds.Location);
        }
    }

好吧,所以在我决定放弃并停止尝试后,我删除了光标的所有痕迹,并开始重新考虑绘制它。修好了。然后我回去访问计时器,发现当不使用从 System.Windows.Forms.Form 派生的 class 时,System.Windows.Forms.Timer 会把你认为的每一盎司都弄乱。因此,我转向 System.Timers.Timer(我之前在移动开发中使用过它并取得了巨大成功)。

之前的问题都解决了,再次感谢您的帮助!我喜欢这个地方。

代码更改如下:

    private double _CursorInterval;
    /// <summary>
    /// Values below 500ms not recommended, due to safety hazards on epilepsy.
    /// </summary>
    public double CursorInterval
    {
        get { return _CursorInterval; }
        set { _CursorInterval = value; }
    }

    private bool blink = false;

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        Height = TextRenderer.MeasureText("A", Font).Height + (2 * BorderWidth) + (2 * TextPadding);
        _InputBounds = new Rectangle(BorderWidth + TextPadding, BorderWidth + TextPadding, Width - (2 * BorderWidth) - (2 * TextPadding), Height - (2 * BorderWidth) - (2 * TextPadding));

        if (blink)
            e.Graphics.FillRectangle(new SolidBrush(BorderColor), new Rectangle(_InputBounds.Location, new Size(1, _InputBounds.Height)));
    }

    System.Timers.Timer blinkTimer;
    protected override void OnGotFocus(EventArgs e)
    {
        if (!DesignMode)
        {
            blinkTimer = new System.Timers.Timer(CursorInterval);
            blinkTimer.Elapsed += TimerElapsed;
            blinkTimer.Start();
            Console.WriteLine("OnGotFocus - I was here.");
        }
        blink = true;
        Invalidate();
        base.OnGotFocus(e);
    }

    private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        blink = !blink;
        Console.WriteLine("TimerElapsed - I was here.");
        Invalidate();
    }

    protected override void OnLostFocus(EventArgs e)
    {
        if(!DesignMode)
        {
            blink = false;
            blinkTimer.Stop();
            blinkTimer.Elapsed -= TimerElapsed;
            Console.WriteLine("OnLostFocus - I was here.");
        }
        Invalidate();
        base.OnLostFocus(e);
    }

    protected override void OnClick(EventArgs e)
    {
        base.OnClick(e);
        Focus();
    }