Laden...

Zwei RichTextBoxen syncron scrollen?

Erstellt von Gregor vor 16 Jahren Letzter Beitrag vor 16 Jahren 2.100 Views
G
Gregor Themenstarter:in
68 Beiträge seit 2005
vor 16 Jahren
Zwei RichTextBoxen syncron scrollen?

[EDIT]Abgeteilt von Elegante lösung 2 RichTextBoxen syncron zu scrollen[EDIT]

Also ich stehe derzeit genau vor dem Problem, dass ich zwei RichTextBox Komponenten synchron scrollen muss. Habe bereits alles versucht was mir eingefallen ist (Get-/SetScrollPos, WM_VSCROLL, anderes zeugs, eine VScroll Komponente und mit dieser dann die beiden Editoren scrollen, ...).

Am besten funktionierte GetScrollPos und SetScrollPos (WinAPI), aber wenn ich mit SetScrollPos eine Position setze, bewegt sich der Inhalt kein Stück (der Text), nur die Scrollbar ändert ihre Position (toll!).

Hat jemand eine Idee wie ich noch den Content zum Bewegen bekomme? Habe jetzt sicher zwei Tage (17 Stunden) damit verbracht 😦.

Naja bin gespannt.

// Edit:
Achja, natürlich die beiden Knöpfe zu drücken (hoch / runter) funktioniert perfekt mit der Methodik wie sie hier gezeigt wird! Aber sobald man den Balken mit der Maus pakt und diesen verschiebt geht nichts mehr. Ich konnte auch mit Spy++ nicht herausfinden welche Message das ganze "steuert". Eingeleitet wird es sicher mit "ThumbTrack" (zu finden zum Beispiel in System.Windows.Forms.ScrollEventType) und mit "ThumbPosition" wohl gestoppt... Es gibt auch ein "EndScroll" welches möglicherweise auch ein Ende anzeigt.... Ach keine Ahnung 🙁

Gruss,
Gregor

2.921 Beiträge seit 2005
vor 16 Jahren

Hallo,

benutze folgende richtextbox-klasse:


using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace TestWindowRect
{

    public class MyScrollEventArgs : EventArgs
    {
        private int m_Position;
        public MyScrollEventArgs(int position)
        {
            m_Position = position;
        }
        public int Position
        {
            get { return m_Position; }
            set { m_Position = value; }
        }
    }

    public class CRichTextBox : RichTextBox
    {
        [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);
        [DllImport("User32.dll")]
        public extern static int GetScrollInfo(IntPtr hWnd, int fnBar, ref ScrollInfo lpsi);

        public event EventHandler<MyScrollEventArgs> VScrollEvent;
        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            switch (m.Msg)
            {
                case (int)Message.WmVScroll:
                    if (VScrollEvent != null)
                    {
                        int pos;
                        ScrollInfo scrollInfo = new ScrollInfo();
                        scrollInfo.cbSize = (uint)Marshal.SizeOf(scrollInfo);
                        if ((m.WParam.ToInt32() & 0xFF) == (int)ScrollBarCommands.ThumbTrack)
                        {
                            scrollInfo.fMask = (uint)ScrollBarInfo.TrackPos;
                            GetScrollInfo(this.Handle, (int)ScrollBarType.SbVert, ref scrollInfo);
                            pos = scrollInfo.nTrackPos;
                        }
                        else
                        {
                            scrollInfo.fMask = (uint)ScrollBarInfo.Pos;
                            GetScrollInfo(this.Handle, (int)ScrollBarType.SbVert, ref scrollInfo);
                            pos = scrollInfo.nPos;
                        }
                        VScrollEvent(this, new MyScrollEventArgs(pos));
                    }
                    break;
            }
            base.WndProc(ref m);
        }
    }
}


und so den Code in der Form:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.Runtime.InteropServices;
namespace TestWindowRect
{
    [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;
    };


    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 partial class Form1 : Form
    {
        CRichTextBox bx1 = new CRichTextBox();
        CRichTextBox bx2 = new CRichTextBox();

        public Form1()
        {
            InitializeComponent();

            bx1.Bounds = richTextBox1.Bounds;
            this.Controls.Remove(richTextBox1);
            bx1.Parent = this;
            bx2.Bounds = richTextBox2.Bounds;
            this.Controls.Remove(richTextBox2);
            bx2.Parent = this;
            bx1.Text = CreateString();
            bx2.Text = CreateString();

            bx1.VScroll += new EventHandler(bx1_VScroll);
        }

        void bx1_VScroll(object sender, EventArgs e)
        {
            int nPos = CRichTextBox.GetScrollPos(bx1.Handle, (int)ScrollBarType.SbVert);
            nPos <<= 16;
            uint wParam = (uint)ScrollBarCommands.ThumbPosition | (uint)nPos;
            CRichTextBox.SendMessage(bx2.Handle, (int)Message.WmVScroll, new IntPtr(wParam), new IntPtr(0));
        }

        private string CreateString()
        {
            string sTemp = string.Empty;
            Random rand = new Random(DateTime.Now.Millisecond);
            for (int i = 0; i < 10000; i++)
            {
                sTemp += (char)rand.Next(64,64+26);
            }
            return sTemp;
        }
    }
}

funktioniert bei mir einwandfrei. 🙂

Wenn bx1 gescrollt wird, scrollt bx2 synchron mit. 🙂

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

G
Gregor Themenstarter:in
68 Beiträge seit 2005
vor 16 Jahren

Danke für den Code, habe die Lösung bereits einmal getestet aber irgendwo einen Fehler gemacht und nicht mehr weiter verfolgt. Hab es heute früh fast identisch auch hinbekommen, ist aber nicht so ganz ideal... Will halt nicht unbedingt immer irgendwelche WM's abschicken und so weiter. Hab jetzt das Ganze mit einem eigenen Control gelösst (GDI+ 😉 ) und hab so auch mehr Möglichkeiten vom Designtechnischen.

Danke trotzdem.

Gruss,
Gregor W.

2.921 Beiträge seit 2005
vor 16 Jahren

dann wären wir dir sehr verbunden, wenn du diese lösung hier posten würdest.... danke

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

G
Gregor Themenstarter:in
68 Beiträge seit 2005
vor 16 Jahren

Würde ja den Code reinlegen, aber es ist zum einen Code den ich während meiner Arbeit geschrieben habe und den kann ich nicht einfach so veröffentlichen... Als auch Code der für mein Problem passt, nicht aber allgemeingültig ist.

Ich beschreibe aber mal in etwa was ich gemacht habe:

1. Neues UserControl erstellen
Als erstes habe ich ein UserControl erstellt (einfach eine neue Klasse die von UserControl erbt) und diesen mit dem VS Designer mit einer VScrollBar und einer HScrollBar ausgestattet. Jedes dieser UserControls stellt einen Editor dar (ich brauche am schluss eigentlich zwei die ja synchron scrollen).
Die VScrollBar hat als Maximalen Wert, den man erreichen kann, die Menge an Zeilen die ich habe. Es gibt den Maximalen Wert und mit etwas rumrechnen kriegt man den Wert, welchen man wirklich erreichen kann mit dem Scrollen. Wie man es genau macht, steht hier drin -> :rtfm:. Habe mal die englische Version hier reinkopiert:

The value of a scroll bar cannot reach its maximum value through user interaction at run time. The maximum value that can be reached is equal to the Maximum property value minus the LargeChange property value plus 1. The maximum value can only be reached programmatically.

Jede Linie ist in einem Directory (derzeit noch) drin. Jede Linie wird noch durch eine interne private Klasse beschrieben, damit ich den Linien weitere Informationen hinzufügen kann (zum Beispiel ein Icon, welches ich links von den Linien zeichne).

So nun zur Draw Methode: Hier iteriere ich einfach über alle Linien die Sichtbar sind. Die 1. Linie entspricht dem Wert von der VScrollBar und die Anzeigbaren Linien kann man aus der Höhe des Controls und der höhe der Schrift (und weiteren Offsets) berechnen.

Scrolling: Nun ich stelle bei meinem UserControl Properties zur Verfügung mit welchen ich den Wert von den beiden Scrollbars setzen kann von aussen und noch Properties damit ich den Maximalen und Minimalen Wert auslesen kann. Achja man muss natürlich noch die Scrollevents nach aussen verfügbar machen 😉, also public event EventHandler VScroll; oder etwas in der Art.
Bei jedem Scroll wird das UserControl "invalidiert" (hm was ist der Ausdruck im Deutschen??), also: this.Invalidate();

2. Zweites User Control für zwei "Editoren
Da wir einen einzelnen Editor ja geschrieben haben, müssen wir nur noch zwei zu einem UserControl zusammenfassen. Das mache ich mit einem weiteren UserControl und einem SplitPanel in welchem jeweils links und rechts einmal mein 1. UserControl drin steckt. Jetzt hänge ich an alle Scroll Events passende EventHandler. In diesen setze ich den neuen Wert von Editor A der Scrollbar von Editor B. Fertig.

Erweiterungen
Wie gesagt hat mein "Editor" (mein UserControl) die Möglichkeit pro Zeile ein Image anzuzeigen. Dieser wird in einem "Image Margin" links vom Text gezeichnet. Diesen Margin fülle ich noch mit einem dunklen Grau. Beim Schreiben der einzelnen Zeilen mit graphic.DrawString() muss man natürlich zur x-Koordinate diesen "Margin" dazurechen 😉.

Damit die Zeilen nicht direkt aufeinander liegen, habe ich noch einen "LINE_MARGIN", also einen zusätzlichen Abstand zwischen zwei Zeilen, welchen ich zur y-Koordinate dazuzähle während dem Zeichnen.

So in etwa habe ichs gemacht. Es ist nicht zwingend performant und auch nicht perfekt, funktioniert aber soweit sehr gut.

Gruss,
Gregor