zynovij
zynovij

Reputation: 195

Winforms draw lines over text box

I have a display that customizes text and as the user customizes it, I want the text to display. One of the customizations available is an "x-out" which should draw a large "X" over all 5 characters of the text. So when the checkbox "X-Out" is checked, it should update the text from this:

enter image description here

To this:

enter image description here

The box itself is a RichTextBox contained within a Panel. I have tried using the Paint event in the panel, but it draws it underneath the RichTextBox (that's the little red lines on the corners). I have also tried using a custom RichTextBox control like at this website. This code does work, but only if I do not set text and color in the RichTextBox. If I set text and color and then check the checkbox, the WndProc method is not called. How do I trigger the WndProc method with Message.msg 15? Here is the code:

Form

public partial class ButtonDataForm : Form
{
  CustomRichTextBox button1;

  public ButtonDataForm()
  {
    InitializeComponent()
  }

  void OnFormLoad(object sender, EventArgs e)
  {
    cstmRichTextBox = new CustomRichTextBox(richTextBox1);
  }

  void OnXOutChecked()
  {
    cstmRichTextBox.IsChecked = xoutCheckbox.Checked;
    cstmRichTextBox.ParentTex.Refresh(); // This does not trigger the WndProc message on cstmRichTextBox
  }
}

CustomRichTextBox

public class CustomRichTextBox : NativeWindow
{
  public RichTextBox ParentTextBox { get; private set; }
  Graphics textBoxGraphics;
  public bool IsChecked { get; set; }

  public CustomRichTextBox(RichTextBox tb)
  {
    ParentTextBox = tb;
    textBoxGraphics = Graphics.FromHwnd(tb.Handle);
    AssignHandle(ParentTextBox.Handle);
  }

  protected override void WndProc(ref Message m}
  {
    switch(m.Msg)
    {
      case 15:
        parentTextBox.Invalidate();
        base.WndProc(ref m);
        DrawXOut();
        break;
      default:
        base.WndProc(ref m);
        break;
    }
  }

  void DrawXOut()
  {
    if (IsChecked)
    {
      Pen xpen = new Pen(ParentTextBox.ForeColor, 3);
      Point topLeft = ParentTextBox.Location;
      int x1 = topLeft.X + ParentTextBox.Width;
      int y1 = topLeft.Y + ParentTextBox.Height;
      Point topRight = new Point(x1, topLeft.Y);
      Point bottomLeft = new Point(topLeft.X, y1);
      Point bottomRight = new Point(x1, y1);
      textBoxGraphics.DrawLine(xpen, topLeft, bottomRight);
      textBoxGraphics.DrawLine(xpen, bottomLeft, topRight); 
    }
  }
}

EDIT:

I tried placing a transparent panel on the bottom panel and drawing inside it when the Paint event fired. The event fires, but nothing showed up in the panel. Am I calculating the location incorrectly?

void OnXOutChecked()
{
  panel1.Refresh();
}

void OnPanel1Paint(object sender, PaintEventArgs e)
{
    Pen xpen = new Pen(Color.Red, 3);
    Point topLeft = panel1.Location;
    int x1 = topLeft.X + panel1.Width;
    int y1 = topLeft.Y + panel1.Height;
    Point topRight = new Point(x1, topLeft.Y);
    Point bottomLeft = new Point(topLeft.X, y1);
    Point bottomRight = new Point(x1, y1);
    e.Graphics.DrawLine(xpen, topLeft, bottomRight);
    e.Graphics.DrawLine(xpen, bottomLeft, topRight);
}

enter image description here

Upvotes: 0

Views: 148

Answers (3)

György Kőszeg
György Kőszeg

Reputation: 18013

I just modified your CustomRichTextBox a bit:

public class CustomRichTextBox : RichTextBox
{
    private bool xOut;

    public bool XOut
    {
        get => xOut;
        set
        {
            xOut = value;
            Invalidate();
        }
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case 15:
                base.WndProc(ref m);
                if (xOut)
                    DrawXOut();
                break;
            default:
                base.WndProc(ref m);
                break;
        }
    }

    private void DrawXOut()
    {
        using var g = Graphics.FromHwnd(Handle);
        g.DrawLine(Pens.Red, 0, 0, Width, Height);
        g.DrawLine(Pens.Red, 0, Height, Width, 0);
    }

    protected override void OnHScroll(EventArgs e)
    {
        base.OnHScroll(e);
        Invalidate();
    }

    protected override void OnVScroll(EventArgs e)
    {
        base.OnVScroll(e);
        Invalidate();
    }
}

enter image description here

Upvotes: 2

Bas Visscher
Bas Visscher

Reputation: 94

To be honest, I don't think this is something you want to be doing. You are abusing the richtextbox for something it wasn't intended to. I don't know your true intentions, but maybe a strike-through might be a valid solution?

My experiences tell me it's better to use the components as-is and don't change them too much.

An actual answer

If you really want to, you could do something like this: (Based on Nicks answer.)

    public class MyTextbox : RichTextBox
    {
        PictureBox pictureBox = new PictureBox();
        public MyTextbox()
        {
            this.Controls.Add(pictureBox);
            pictureBox.Dock = DockStyle.Fill;
            pictureBox.BackColor = Color.Transparent;
            pictureBox.Paint += PictureBox_Paint;
            pictureBox.BringToFront();
        }

        private void PictureBox_Paint(object? sender, PaintEventArgs e)
        {
            var pen = new Pen(this.ForeColor);
            e.Graphics.DrawLine(pen, 0, 0, this.Width, this.Height);
            e.Graphics.DrawLine(pen, 0, this.Height, this.Width, 0);
        }
    }

Upvotes: 0

Nick
Nick

Reputation: 5042

This is a very unreliable approach, drawing on top of the control and expecting it to work in all possible scenarios (and there can be many!).

You can just place a transparent picture box on top of the text box with a transparent bitmap that has two crossed red lines.

Upvotes: 0

Related Questions