Windows Forms: Scrollable User Control

 

Początek nowego tygodnia, trzeba zatem przystąpić do rozwijania aplikacji konkursowej. Zanim jednak pojawią się kolejne posty związane z tą aplikacją, chciałbym przedstawić temat niekoniecznie ściśle z nią związany. Zbliżający się nieuchronnie początek nowego roku akademickiego jest dla mnie bodźcem do odświeżenia swojej pracy inżynierskiej, która po przebudowie służyć będzie jako podstawa dla pracy magisterskiej. Przeglądając stworzony przeze mnie kod poszukuje rozwiązań, które pozwolą na większą elastyczność i skalowalność kodu. W ostatnim czasie moja uwaga została skupiona na ujednoliceniu sposobu rysowania pewnych obiektów, a tym samym stworzeniu kontrolki w stylu Graph. W stworzonym przeze mnie programie konieczne było rysowanie sygnałów EKG o w zasadzie dowolnej długości. Z tego powodu nie można było tego dokonać np. z użyciem kontrolki PictureBox. Wyświetlane sygnały mogły zajmować oczywiście więcej niż jeden ekran, aplikacja musiała zatem umożliwiać scrollowanie kontrolki, na której przeprowadzone było rysowanie (w przypadku realizacji w C# było to po prostu rysowanie na panelu). Rozwiązanie, aczkolwiek nienajzwinniejsze (dlatego właśnie niezbędne jest utworzenie nowej kontrolki), wymagało odnalezienia sposobu, w jaki zapewnić poprawne rysowanie  przesuwanych obrazów. Przedstawię je na przykładzie tworzonej właśnie przeze mnie kontrolki Graph.

Jak wiadomo, nawet wersja Express narzędzia Visual Studio pozwala na utworzenie tzw. UserControl, czyli kontrolki o wyglądzie i funkcjach zaimplementowanych przez użytkownika. Projektując layout możemy korzystać z elementów znajdujących się w Toolboxie. Chciałbym więc mieć możliwość jednocześnie rysowania, jak i dodawania, być może potrzebnych w przyszłości, innych kontrolek. Z tego względu rysowanie wybranych przeze mnie sygnałów EKG odbywać będzię się na panelu umieszczonym wewnątrz tworzonej przeze mnie kontrolki o nazwie “MyPlainGraphControl”. Do rysowanie własnych obiektów korzystam ze zdarzenia Paint utworzonego panelu. W zdarzeniu tym tworzony jest obiekt typu Graphics, który wykorzystywany jest do narysowania, dla uproszczenia przykładu, linii (dokładnie mówiąc odcinka 😉 ). Kod przybiera w tym przypadku poniższą postać:

private void panel1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    DrawAxes(g);
    g.Dispose();
}

W celu rozpoczęcia rysowania należy ustalić jakiś punkt na płaszczyźnie, z którego ma się ono rozpocząć. Fragment mojej metody inicjalizującej zmienne wygląda następująco:
 
private void Initialization()
{
    ...
    //Both variables type - float
    originX = 0;
    originY = panel1.Size.Height;
    ...
}

Wykorzystana zmienna panel1 to oczywiście nazwa panelu dodanego do kontrolki. Po takiej inicjalizacji można przystąpić do napisania ciała metody rysującej linię:
 
private void DrawAxes(Graphics g)
{
   Pen p = new Pen(Color.Red);
      
   g.DrawLine(p, originX, originY, 500, 0);
   panel1.AutoScrollMinSize = new Size(500, panel1.Size.Height+10);
      
   p.Dispose();
}

Standardowe wykorzystanie obiektu typu Pen do rysowania i jego późniejsze usunięcie nie wymaga komentarza. Linia rysowana jest po przekątnej panelu, przy czym wpisana na sztywno, dla ułatwienia przykładu, wartość 500, wykracza poza ustaloną na początku szerokość panelu, która wynosi nieco ponad 280. Po ustawieniu właściwości AutoScroll na true dla panelu, możliwe jest wykorzystanie pasków przewijania w celu obejrzenia całości wyświetlanej treści. Aby jednak panel mógł skorzystać z tej właściwości konieczne jest poinformowanie go o takiej potrzebie, czyli rozszerzenie pola na którym odbywa się rysowanie. Zastosowana przeze mnie metoda, polegająca na skorzystaniu z właściwości AutoScrollMinSize, znakomicie nadaje się do tego celu.

Marzeniem każdego programisty byłoby, gdyby takie proste rozwiązanie było skuteczne. W końcu rozszerzyliśmy zgodnie z zaleceniem pole do rysowania, pojawiają się scrollbary umożliwiające przewijanie obrazu… ale otrzymany efekt przy przewijaniu nieoczekiwanie wygląda tak:

zle przewijanie

To,  co znajduje się na powyższym rysunku w żaden sposób nie przypomina oczekiwanego ukośnego odcinka w przekątnej panelu. Swojego czasu długie godziny spędzone na rozgryzieniu tego zadania zaowocowały odnalezieniem na jednej ze stron (w tym momencie nie jestem w stanie przytoczyć źródła) następującego rozwiązania:

private void panel1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    
    //Before any paintings - new lines added
    Matrix mx = new Matrix(1, 0, 0, 1, panel1.AutoScrollPosition.X,
        panel1.AutoScrollPosition.Y);
    e.Graphics.Transform = mx;
    
    DrawAxes(g);
    g.Dispose();
}

W kodzie pojawił się nowy fragment poniżej komentarza. Zastosowanie takiego rozwiązania prowadzi do osiągnięcia pożądanego rezultatu:

dobre przewijanie

Użycie obiektu typu Matrix wymaga dodania następującej przestrzenii nazw:

using System.Drawing.Drawing2D;

Wykorzystania macierzy transformacji do przedstawionego rozwiązania nie będę omawiał ze względu na to, że nie miałem do czynienia z przetwarzaniem obrazów, a temat ten jest z pewnością obszerny, dlatego zachęcam do samodzielnego zgłębienia tematu w razie potrzeby.
Reklamy

2 Responses to Windows Forms: Scrollable User Control

  1. Michał Iwanowski says:

    Problemem będzie szerokość panelu większa od 32,767 pixeli. Mimo że Width dla kontrolki jest typu Int32 🙂 nic większego nie przejdzie.
    To ci zagadka:)

    • Hmm przyznam, że tego akurat nie sprawdziłem. Dla moich zastosowań do sygnałów EKG sygnał i tak jest dzielony „na strony”, więc nawet bym tego nie zauważył. Dziękuję za zwrócenie uwagi na problem 🙂

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

%d blogerów lubi to: