question

RobertGustafson-1682 avatar image
0 Votes"
RobertGustafson-1682 asked RobertGustafson-1682 edited

Can I use the SetWindowsSubclass API to subclass a constituent control on a UserControl?

WHAT I HAVE:
Visual Basic 2019, WinForms, .NET 4+

MY PROBLEM:
I've created an extended-function rich-text UserControl that uses a RichTextBox as a child constituent control, and I want to use the SetWindowsSubclass on the constituent control to display page breaks when the text-box repaints (RichTextBox--and TextBox, for that matter--don't fire Paint events). I could create an extended control that inherits from the RichTextBox, and use that as the constituent control within my UserControl (relying on WndProc)--but that means adding an additional level of complexity and an additional project to my class-library solution--and it means end users will have to reference an additional library besides the UserControl library in order to place my UserControl into a host project! (They already have to reference an extra one on account of the spell-checker!)

I've been able to use SetWindowSubclass on a regular RichTextBox from within a form, but when I try to port the code to my UserControl code, it (apparently) sucessfully installs the subclass, but it doesn't fire the subclass procedure designed to handle the WM_PAINT message. It works fine when the RichTextBox is directly on a form, but not when it's a constituent inside a UserControl!

What am I doing wrong?! I need an answer ASAP, and in VB.NET.

PS. My tentative solution is to place the page-break drawing code inside of a Timer_Tick event that fires every so many times a second. The result is that the page-breaks have to be continually updated--even when text isn't changed, which shouldn't be necessary, but it is when done this way ('cause there's no way to know when the control will be repainted)--and there's flicker with respect to the page-break lines every time text is changed.

dotnet-visual-basicwindows-forms
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

RobertGustafson-1682 avatar image
0 Votes"
RobertGustafson-1682 answered RobertGustafson-1682 edited

I appear to have solved the problem. The thing is, I have to use the tmrPageBreaks.Tick event of a Timer component, to plug in the subclass rather than the rtb.HandleCreated event of the RichTextBox, as follows:


 '   TIMER EVENT PROCEDURES:
    
 '   Display page breaks

 Private Sub tmrPageBreaks_Tick(sender As Object, e As EventArgs) Handles tmrPageBreaks.Tick
     Debug.WriteLine("INSIDE TIMER")
     SetUpSubclassIfNeeded()
     If IsRTBSubclassPresent Then
         tmrPageBreaks.Enabled = False
     End If

' DrawPageBreaks()
End Sub

For some reason, invoking SetUpSubclassIfNeeded()--which plugs the subclasser in if it isn't already--can't be reliably/sustainable done simply when the RichTextBox's handle is created. (Note that the timer is no longer needed once the subclasser is verified to be in place.)

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Castorix31 avatar image
0 Votes"
Castorix31 answered RobertGustafson-1682 commented

I just tested a UserControl and a RichTextBox (RichEdit) as child
(Public Class UserControlTest
Inherits UserControl

then
LoadLibrary("msftedit.dll")
CreateWindowEx with "RichEdit50W" class
)

and I have no problem to use SetWindowSubclass
I just drew an Ellipse in WM_PAINT for testing : it is behind if I subclass the UserControl, or above for the RichEdit control :

RichEdit subclassed :
137897-usercontrol-richedit-paint.jpg


UserControl subclassed :
137962-usercontrol-richedit-paint2.jpg







· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

See my "answer" below (which is actually another question).

0 Votes 0 ·
RobertGustafson-1682 avatar image
0 Votes"
RobertGustafson-1682 answered RobertGustafson-1682 commented

@Castorix31:

I'm using the standard WinForms RichTextBox-class control in the UserControl designer, not the RichEdit-class library via Win32 API at run-time using LoadLibrary or CreateWindowEx. I did everything the "conventional" way for a UserControl with constituent controls--although the Modifiers property of the RichTextBox control is set to Public so that the end user can access its members from an external host project that uses the larger control . (In case you're wondering, changing the property doesn't seem to mitigate my predicament.) So why is it that the same VB.NET Win32 code that works for the control when it's on the form by itself doesn't work when it's a constituent control inside another?! My problem is that the subclass procedure I've delegated to doesn't even get triggered even though SetWindowSubclass--which is supposed to plug it in--returns True--meaning that the installation was "successful".

Do the following (and in VB.NET), just to make sure we're making an apples-to-apples comparison: Set up UserControlTest in the designer, dropping a WinForms RichTextBox control onto the larger control's designer space. (Don't use CreateWindowEx to create the text box via API; also experiment with different Modifiers settings for the constituent RichTextBox control.) Then subclass the constituent RichTextBox control using SetWindowSubclass (and RemoveWindowSubclass). See what happens. (And show me the subclass-relevant code so I can see if your code and mine is fundamentally any different.)

PS.
1. The only proviso for the SetWindowsSubclass API in the help file is that it can't be used to subclass across threads. That shouldn't be an issue since everything's on the UI thread. (Is it??)
2. Not only does SetWindowsSubclass actually work when used on a RichTextBox control that's directly on a form by itself (but not when used on a RichTextBox control that's a constituent control), it also works on "auxillary" windows of a control whose handles aren't exposed by the control. For instance, I've created an extended control that inherits from the ComboBox control and subclasses the drop-down window of it. Is the problem specific to standard WinForms controls that are placed on UserControl controls as constituents?! Experiment and find out for me and get back to me!






· 3
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

I'm using the standard WinForms RichTextBox-class control, not the RichEdit-class.

It is the same control
I tested first with Win32 APIs to avoid some .NET subclassing which can occur, but I tested also with RichTextBox and Controls.Add and SetWindowSubclass still works
If the handle is ok, SetWindowSubclass always works

Usual declarations :

    <DllImport("Comctl32.dll", SetLastError:=True)>
     Public Shared Function SetWindowSubclass(hWnd As IntPtr, pfnSubclass As SUBCLASSPROC, uIdSubclass As UInteger, dwRefData As UInteger) As Boolean
     End Function
    
     <DllImport("Comctl32.dll", SetLastError:=True)>
     Public Shared Function DefSubclassProc(hWnd As IntPtr, uMsg As UInteger, wParam As IntPtr, lParam As IntPtr) As Integer
     End Function
    
     Public Delegate Function SUBCLASSPROC(hWnd As IntPtr, uMsg As UInteger, wParam As IntPtr, lParam As IntPtr, uIdSubclass As IntPtr, dwRefData As UInteger) As Integer

then

  Public NewSubClassDelegate As SUBCLASSPROC

then

 NewSubClassDelegate = AddressOf NewSubClassProc
 SetWindowSubclass(Me.Handle, NewSubClassDelegate, 0, 0)









0 Votes 0 ·

See my "answer" below. It actually shows my subclass-relevant code.

0 Votes 0 ·

I don't use Me.Handle, since I'm subclassing the constituent control, not the overall UserControl. See below.

0 Votes 0 ·