2

I want to sync the scrolling of two Winforms Richtextboxes. When RTB2 gets scolled, RTB1 needs to be exactly aligned all the time. I tried to convert this c#-Code here LINK (second answer), but failed so far. So I need help. Right now it produces multiple errors:

Type [ScrollBarCommands] was not found...
Type [ScrollBarType] was not found....
Type [Message] was not found...
Also "illegal conversions" and so on.

This is an example script:

[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$form = New-Object system.Windows.Forms.Form
$form.size  = "400,400"
$rtb1 = New-Object system.Windows.Forms.RichTextBox
$rtb1.size  = "190,350"
$rtb1.location = "200,1"
$rtb1.text = (1..300 | out-string)
$form.controls.add($rtb1)
$rtb2 = New-Object system.Windows.Forms.RichTextBox
$rtb2.size  = "190,350"
$rtb2.location = "1,1"
$rtb2.text = (1..300 | out-string)
$rtb2.scrollbars = "none"
$form.controls.add($rtb2)

$code = @'
public enum ScrollBarType : uint {
   SbHorz = 0,
   SbVert = 1,
   SbCtl = 2,
   SbBoth = 3
 }
public enum Message : uint {
   WM_VSCROLL = 0x0115
}
public enum ScrollBarCommands : uint {
   SB_THUMBPOSITION = 4
}
[DllImport( "User32.dll" )]
public extern static int GetScrollPos( IntPtr hWnd, int nBar );
[DllImport( "User32.dll" )]
public extern static int SendMessage( IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam );
'@
Add-Type -Name WinUtils -MemberDefinition $code -Namespace User32

$rtb1.add_VScroll({ 
   [uint32]$nPos = [User32.WinUtils]::GetScrollPos( $rtb1.Handle, [ScrollBarType]::SbVert )
   [uint32]$npos = $nPos -shl 16
   [uint32]$wParam = [ScrollBarCommands]::SB_THUMBPOSITION -bor $nPos
   [User32.WinUtils]::SendMessage( $rtb2.Handle, [Message]::WM_VSCROLL, $wParam , [ref]0)
})

$form.showdialog()

Editors, please note: It's not a duplicate, this is about **Powershell. :)**

Moss
  • 325
  • 2
  • 16

1 Answers1

1

ScrollBarType, ScrollBarCommands, and Message are members of User32.WinUtils namespace. Also, ptr should be System.IntPtr.

EDIT: To scroll $rtb2 while dragging the scrollbar (and not just when mouse is released), you have to use GetScrollInfo

EDIT 2: Fixed arrow scroll buttons not working

EDIT 3: Fixed other bugs. Restructured code.

$typeDef = @"
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public enum ScrollBarType : uint {
    SbHorz = 0,
    SbVert = 1,
    SbCtl  = 2,
    SbBoth = 3
}

public enum Message : uint {
    WmVScroll = 0x0115
}

public enum ScrollBarCommands : uint {
    ThumbPosition = 4,
    ThumbTrack    = 5
}

[Flags()]
public enum ScrollBarInfo : uint {
    Range           = 0x0001,
    Page            = 0x0002,
    Pos             = 0x0004,
    DisableNoScroll = 0x0008,
    TrackPos        = 0x0010,

    All = ( Range | Page | Pos | TrackPos )
}

public class CustomRichTextBox : RichTextBox {
    public Control Buddy { get; set; }

    public bool ThumbTrack = false;

    [StructLayout( LayoutKind.Sequential )]
    public struct ScrollInfo {
        public uint cbSize;
        public uint fMask;
        public int nMin;
        public int nMax;
        public uint nPage;
        public int nPos;
        public int nTrackPos;
    };

    [DllImport( "User32.dll" )]
    public extern static int SendMessage( IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam );
    [DllImport( "User32.dll" )]
    public extern static int GetScrollInfo( IntPtr hWnd, int fnBar, ref ScrollInfo lpsi );

    public void CustomVScroll() {
        int nPos;

        ScrollInfo scrollInfo = new ScrollInfo();
        scrollInfo.cbSize = (uint)Marshal.SizeOf( scrollInfo );

        if (ThumbTrack) {
            scrollInfo.fMask = (uint)ScrollBarInfo.TrackPos;
            GetScrollInfo( this.Handle, (int)ScrollBarType.SbVert, ref scrollInfo );
            nPos = scrollInfo.nTrackPos;
        } else {
            scrollInfo.fMask = (uint)ScrollBarInfo.Pos;
            GetScrollInfo( this.Handle, (int)ScrollBarType.SbVert, ref scrollInfo );
            nPos = scrollInfo.nPos;
        }

        nPos <<= 16;
        uint wParam = (uint)ScrollBarCommands.ThumbPosition | (uint)nPos;
        SendMessage( Buddy.Handle, (int)Message.WmVScroll, new IntPtr( wParam ), new IntPtr( 0 ) );
    }

    protected override void WndProc( ref System.Windows.Forms.Message m ) {
        if ( m.Msg == (int)Message.WmVScroll ) {
            if ( ( m.WParam.ToInt32() & 0xFF ) == (int)ScrollBarCommands.ThumbTrack ) {
                ThumbTrack = true;
            } else {
                ThumbTrack = false;
            }
        }

        base.WndProc( ref m );
    }
}
"@

$assemblies = ("System.Windows.Forms", "System.Runtime.InteropServices")

Add-Type -ReferencedAssemblies $assemblies -TypeDefinition $typeDef -Language CSharp

### Form

$form = New-Object System.Windows.Forms.Form
$form.Size = "400,400"

### Rich text box 1 (Synchronized - master)

$rtb1 = New-Object CustomRichTextBox

$rtb1.Size = "190,350"
$rtb1.Location = "200,1"
$rtb1.Text = (1..300 | Out-String)

$form.Controls.Add($rtb1)

### Rich text box 2 (Synchronized - slave)

$rtb2 = New-Object system.Windows.Forms.RichTextBox

$rtb2.Size = "190,350"
$rtb2.Location = "1,1"
$rtb2.Text = (1..300 | Out-String)
$rtb2.ScrollBars = "none"

$form.Controls.Add($rtb2)

### Synchronization setup

$rtb1.Buddy = $rtb2

$rtb1.Add_VScroll({
    $this.CustomVScroll()
})

### Run

$form.ShowDialog()
Božo Stojković
  • 2,893
  • 1
  • 27
  • 52
  • 1
    Excellent thanks!, Just one more question: right now the RTB1 gets only scolled when the user is done scrolling (mouse button release), is there any way to tweak that so that RTB1 gets scrolled while the user is still scrolling? – Moss Mar 02 '18 at 17:06
  • 1
    Not sure what you mean? – Božo Stojković Mar 02 '18 at 17:09
  • 1
    Just try it, and you see what I mean :) When you scroll the right box, the left one gets only scrolled when you release the scroll-bar and stop scrolling. This is fine, but ideally the would both scroll simultaneously. I jut saw: this only occurs if you scoll woth the mouse and the scroll-bar. – Moss Mar 02 '18 at 17:11
  • 1
    Oh, I didn't notice that, as I was using scrollwheel, which works alright. Let me see if I can get that working too.. – Božo Stojković Mar 02 '18 at 17:12
  • 1
    For some reason, `[User32.WinUtils]::GetScrollPos( $rtb1.Handle, [User32.WinUtils+ScrollBarType]::SbVert )` is not updating while dragging the scroll bar. – Božo Stojković Mar 02 '18 at 17:38
  • So no chance? :) – Moss Mar 02 '18 at 17:48
  • 1
    I can't seem to find anything yet. I'll keep searching. – Božo Stojković Mar 02 '18 at 17:50
  • 1
    You are using `SB_THUMBPOSITION`, and it should be `SB_THUMBTRACK`. " This message is sent repeatedly until the user releases the mouse button." But setting it doesn't work. – Božo Stojković Mar 02 '18 at 18:10
  • There is also `GetScrollInfo`, which should fit this case. Just have to get it working. – Božo Stojković Mar 02 '18 at 18:28
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/166124/discussion-between-bozo-stojkovic-and-moss). – Božo Stojković Mar 02 '18 at 18:34
  • I nearly missed this because when you edit, I dont get a notification. Anyway, this is some excellent coding, thanks a lot for your help! – Moss Mar 03 '18 at 16:12
  • I thought you will get notified by the room.. Glad I helped. It was a nice exercise. – Božo Stojković Mar 03 '18 at 16:15
  • Hey :) One odd thing though: If you hold the scrollbar and drag up, it doesent stop scolling the other RTB at line one, but jumps to the point where you started. Difficult to explain. :) Its not a biggy, but is there an easy way to simply stop scolling up when line 1 is reached? – Moss Mar 04 '18 at 11:51
  • Could you proceed any discussion in the chatroom, please? Thanks. – Božo Stojković Mar 04 '18 at 12:53