1

I'm trying to elaborate a TextCtrl with some line drawings: simple stuff I want to draw on the text, next to it, between lines, etc. And I'm approaching drawing in Wx like I've done it before, on WxPanels: with DCs: $dc->DrawLine( 15, 15, 120, 120); etc.

So far, I'm doing this: in a class derived from TextCtrl, I catch EVT_PAINT and implement my own OnPaint function:

sub new {
     my $class  = shift;
     my $parent = shift;

     my $self = $class->SUPER::new( $parent, -1, '', wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);

     EVT_PAINT($self, \&OnPaint );

     return $self;
}

sub OnPaint {
    print"OnPaint: @_ \n";

    # @ravenspoint: this is there I would call the base's paint method
    # first, right? But how??

    my $linesCnt = $_[0]->GetNumberOfLines();

        my $dc = Wx::PaintDC->new($_[0]);
        $dc->DrawLine(
            15, 15,
            120, 120
        );

    # commented out as the base class' paint method will follow,
    # erasing what we've just drawn
    #   $_[1]->Skip(1);
};

Now, the thing is: either the line or the text in the control is drawn, when I let the paint event propagate up or not. For any Wx guru, this might be obvious, but for me, it's not. As I understand a TextCtrl, it is an agglomerate of a WxWindow, and a low level way of drawing text into this, with a dc, and DrawText() and some convenience functions on top, to set the caret etc.

This understanding means for me: to be able to draw, I would need access to the already prepared/drawn-upon dc so I can get on drawing onto it. Right? Is that possible, in general, and in WxPerl? In my function above, I'm doing my paint before the control draws the text, that's what I think is the cause for the line disappearing when I uncomment Skip() - as the control then clears and redraws the dc.

Is there a way to get into the loop after the TextCtrl has done all it's drawing?

P.S.

Plain TextCtrl seems to be implemented by calling a native text entry on most platforms, although the docs don't mention that.

I dug around in Kephra and Padre, which both show a "right margin indicator", a thin vertical line usually grey, drawn onto the text area. How did they do it? Surprise: they subclass Wx::Scintilla or the older Wx::StyledTextCtrl which both offer the SetEdgeColumn() method to trigger that.

Asking myself if reimplementing my own text widget is the only way to get access/hook into the drawing of text onto the DC. (Shaking head in disbelief)

Tinkering with *EVT_ERASE_BACKGROUND* and RichtTextCtrl's PaintBackground() without success (well, after a few minutes of hacking).

isync
  • 537
  • 6
  • 15

3 Answers3

3

This is not an answer you want to hear, but you can't draw on a native control (and wxTextCtrl is one such). It draws itself already and you can't interfere with its drawing logic, especially considering that it's different under different platforms. So while you can make it work sometimes (especially under MSW where just anything is possible, i.e. you're completely free to shoot yourself in the foot in many different ways), this is not guaranteed to work under all platforms and you really shouldn't do it at all.

If you want some advanced capabilities not provided by wxTextCtrl, you should probably look at wxRichTextCtrl instead. It is also implemented entirely in wxWidgets itself, i.e. is not native, and so you can draw over it.

VZ.
  • 21,740
  • 3
  • 39
  • 42
  • 1
    Most of wxWidgets controls are native widgets and those that are not can be reimplemented using native support in future versions of the library and/or the underlying toolkit (this happens a lot with wxGTK). – VZ. Mar 24 '14 at 15:15
  • It's not mentioned here so far, but that TextCtrl might be implemented by harnessing a native widget was another possibility. But the docs are mute about that. So, thumbs up for confirming that TextCtrl is a native control. Although I had no luck in drawing on RichTextCtrl either... – isync Mar 24 '14 at 15:16
0

In your function that overrides the base class paint method, call the base class paint method before your own code.

For what it is worth, here is how to do it with C++

wxTextCtrl::OnPaint()
ravenspoint
  • 19,093
  • 6
  • 57
  • 103
  • Ah, okay. But I can't figure out how to do that in WxPerl. Any hint? Calling $self->SUPER::OnPaint() does not work. Also: would Wx keep track of the fact that the base's paint had already been called? But apart from that, it seems I only have access to the base class' new().., not paint stuff.. right? (Probably making a fool out of me here...) – isync Mar 23 '14 at 18:59
  • Sorry, I know nothing about wxPerl. It looks like a crazy language! A good perl textbook should tell you how to call a method in the base class. – ravenspoint Mar 23 '14 at 20:29
  • Well, apart from _$self->SUPER::OnPaint()_ (with SUPER being a Perlish shorthand for the "base class"), I've tried `Wx::TextCtrl::OnPaint()` as well, but that didn't seem to do anything, did not throw and error but also had no effect. Interesting is switching between subclassing TextCtrl and RichtTextCtrl (with the latter I ended up with a line but no text showing up, while with TextCtrl it was the other way 'round). I'm getting the hunch that WxPerl's "bindings of some sort" into Wx are incomplete when it comes to OnPaint... – isync Mar 23 '14 at 20:59
  • 2
    `wxTextCtrl::OnPaint()` is not a documented part of wxWidgets API (and doesn't actually even exist) and should never be called explicitly. To let the base class processing take place, you simply call `event.Skip()` in your event handler. – VZ. Mar 24 '14 at 13:12
  • With Skip(), I've always had the association that it is like calling return() - it would immediately exit that subroutine. Well, that's not the case, but nevertheless, calling Skip() early in my OnPaint routine, and then doing my painting on the DC has no effect, though. – isync Mar 24 '14 at 15:11
  • `Skip()` is absolutely not like calling return, I won't go into this in this comment but you really need to understand the difference between them. – VZ. Mar 24 '14 at 15:14
0

I am trying to act on VZ's advice. Below is a minimal program intended to create a RichTextCtrl and draw a line across it. Instead it only draws a RichTextCtrl box. If I comment out the line beginning with "self.rtc = ", it instead draws only the line, not the RichTextContrl.

# sources:
  # http://wiki.wxpython.org/VerySimpleDrawing
  # https://github.com/wxWidgets/wxPython/blob/master/demo/RichTextCtrl.py

import wx
import wx.richtext as rt

class DrawPanel(wx.Frame):
    def __init__(self, *args, **kw):
        wx.Frame.__init__(self, *args, **kw)
        self.rtc = rt.RichTextCtrl(self,style=wx.VSCROLL|wx.HSCROLL|wx.NO_BORDER)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def OnPaint(self, event=None):
        dc = wx.PaintDC(self)
        dc.Clear()
        dc.SetPen(wx.Pen(wx.BLACK, 2))
        dc.DrawLine(0, 0, 50, 50)

app = wx.App(False)
frame = DrawPanel(None)
frame.Show()
app.MainLoop()
Jeffrey Benjamin Brown
  • 3,427
  • 2
  • 28
  • 40