TFS2010 – find changeset for label – quick tip

Management of labels in TFS 2010 using Visual Studio 2010 is not especially user friendly. Recently I’ve faced a situation when I had to find for which changeset label has been created. The simplest solution appeared to be usage of developer command prompt and invoking command in local solution workspace:

tf history . /stopafter:1 /noprompt /r /version:LNameOfMyLabel

Please note capital L after version parameter with colon. This L is used to mark that search should be performed for label with specified name after it.

 

 

Reklamy

FCKeditor – edytor WYSIWYG HTML

 

Dzisiaj chciałbym przedstawić proces dodawania wizualnego edytora html w projekcie ASP.NET. Edytor taki może być przydatny w momencie, gdy chcemy mieć do dyspozycji łatwy sposób wprowadzania treści umieszczanych potem na stronie. Chodzi tutaj o możliwość wprowadzania nie tylko czystego tekstu, ale także formatowanego (pogrubienie, kursywa), umieszczania obrazków, filmów YouTube itd. Tutaj z pomocą przychodzi nam darmowy FCKeditor (w najnowszej wersji przemianowany na CKEditor) rozprowadzany na licencjach GPL, LGPL, MPL. W tym tutorialu przedstawiony zostanie opis dla nieco starszej wersji oznaczonej jako FCKeditor 2.6.6.

Edytor można pobrać ze strony http://ckeditor.com/download. W przypadku ASP.NET jedną z dróg (przedstawianą przeze mnie) jest pobranie dwóch plików. Są to: wspomniany FCKeditor 2.6.6 oraz FCKeditor.Net 2.6.4. W dużym skrócie – po rozpakowaniu pierwszego pliku otrzymujemy wszelkie źródła .js, natomiast po rozpakowaniu drugiego pliku uzyskujemy dostęp do .dll,  do której musimy dodać referencję w projekcie. Odpowiedni plik można odszukać w katalogu ~/FCKeditor.Net 2.6.4/bin/Release/2.0/FredCK.FCKeditorV2.dll. Po dodaniu samej referencji edytor jednak nie zadziała. Konieczne jest również dodanie do projektu katalogu ze “źródłami”, co można uczynić najprościej przechodząc do rozpakowanego katalogu FCKeditor 2.6.6 i wybierając opcję kopiowania folderu fckeditor, a następnie wklejając katalog w solucji projektu. Wynik wymienionych czynności powinien wyglądać następująco:

2011-02-06 part 1

Szczególnie interesujące są oczywiście pozycje wyróżnione kolorem żółtym.

W celu użycia FCKeditora na stronie .aspx niezbędne jest dodanie na początku pliku wpisu

<%@ Register Assembly="FredCK.FCKeditorV2" Namespace="FredCK.FCKeditorV2" TagPrefix="FCKeditorV2" %>

Samą kontrolkę natomiast umieszcza się dodając kod:

<FCKeditorV2:FCKeditor ID="FCKeditor1" runat="server" BasePath="~/fckeditor/" ToolbarSet="Basic">
</FCKeditorV2:FCKeditor>

Szczególnie interesujące są tutaj dwie właściwości BasePath oraz ToolbarSet. Pierwsza z nich wskazuje na położenie katalogu wewnątrz solucji ze źródłami edytora. Druga natomiast określa zestaw opcji dostępnych do wybrania podczas edycji tekstu. W przypadku niepodania drugiej właściwości przyjęte jest, że wyświetla się domyślny zestaw opcji. Okno edytora wygląda wtedy następująco:

2011-02-06 part 2

Nie każdemu jednak taki zestaw opcji jest niezbędny do działania. Istnieje tutaj dowolna możliwość konfiguracji. Jeśli chodzi o ustawienia FCKeditora warto zainteresować się plikiem w podkatalogu fckeditor o nazwie fckconfig.js. Odnajdując odpowiednią sekcję ToolbarSets można zobaczyć ustawienia w rodzaju tych umieszczonych na rysunku poniżej:

2011-02-06 part 3

Z powyższego rysunku wynika, że FCKeditor dysponuje dwoma wbudowanymi zestawami paska opcji – Default oraz Basic, które to mogą być ustawione poprzez wspomnianą wcześniej właściwość kontrolki (uwaga na powyższym rysunku znalazła się opcja YouTube, która nie jest standardowo dostępna). Oczywiście opcje dostępne na pasku Basic są podzbiorem opcji paska Default, aczkolwiek możliwe jest korzystanie z pluginów, które wzbogacają znacznie funkcjonalność edytora.

Grzebanie w opcjach konfiguracyjnych dostarczonych przez producenta ma jednak dwie wady. Po pierwsze istnieje możliwość utracenia starej zawartości pliku, jeśli ktoś nie komentuje istniejących do tej pory wpisów. Po drugie, tracimy na mobilności. Jeśli zmieni się wersja edytora i trzeba będzie podmienić katalog ze źródłami, bądź też zajdzie taka potrzeba z jakiegoś innego powodu, tracimy nasze dodane ustawienia. Z tego powodu warto przechowywać własne ustawienia w osobnym pliku, który można umieścić np. w katalogu głównym solucji, nie tracąc go przy zmianie wersji edytora.

Zdefiniujmy zatem własny, skromny pasek dla edytora w osobnym pliku konfiguracyjnym. W tym celu tworzymy nowy plik o nazwie np. fckcustom_config.js w katalogu głównym solucji (przy profesjonalnych projektach warto mieć osobny katalog dla takich plików). W pliku tym dodajemy analogicznie do wpisu na poprzednim rysunku następujący kod:

2011-02-06 part 35

Jak łatwo się domyślić, w pasku edytora będą jedynie opcje pogrubienia, kursywy, podkreślenia, oraz wstawienia listy uporządkowanej i nieuporządkowanej. Znak ‘-‘ oznacza graficzny separator pomiędzy opcjami.

Aby możliwe było korzystanie z dodatkowego pliku konfiguracyjnego należy jeszcze dodać w istniejącym do tej pory pliku fckconfig.js wpis mówiący o ścieżce do pliku z dodatkową konfiguracją. W moim przypadku ma on postać:

FCKConfig.CustomConfigurationsPath = ‚/fckcustom_config.js’;

Teraz właściwość kontrolki można ustawić na ToolbarSet = “MyOptions” i po uruchomieniu programu cieszyć się takim oto widokiem spersonalizowanego paska opcji:

2011-02-06 part 4

Warsztat programisty – FxCop

 

W ramach przygotowania do dalszej pracy podjąłem zamiar zwiększenia jakości tworzonego kodu. Narzędzi zwiększających produktywność programisty jest oczywiście wiele. Osobom znającym już jednak podstawy, na początek, polecam zapoznanie się z programem FxCop. Jest to darmowe narzędzie od Microsoftu służące do przeprowadzania statycznej analizy kodu (oczywiście osoby dysponujące wersjami Visual Studio innymi niż Express nie muszą w tym celu instalować FxCop). Sprawdzenie odbywa się na podstawie wytycznych dla platformy .NET zwanych Design Guidelines. Dotyczą one wielu aspektów tworzonego kodu, jak np. nazewnictwa metod, zaleceń odpowiedniego wykorzystania właściwości, czy też zaleceń tworzenia metod statycznych. Doświadczony programista z pewnością zna większość tych reguł na pamięć, jednak osoby mniej doświadczone mogą być zdziwione po uruchomieniu analizy, jak wiele błędów w projektowaniu popełniły Smile Warto również wspomnieć, że jedną z osób należących do teamu FxCop był polak Krzysztof Cwalina, który to miał niemały wkład zarówno w tworzenie elementów Frameworka, jak i wspomnianych wcześniej reguł projektowania.

Co do samej statycznej analizy kodu, z pewnością nie jest to remedium na wszystkie bolączki. Podobnie jak w przypadku każdego narzędzia najważniejsze jest korzystanie z niego z głową i nie na ślepo. O ile bowiem błędy w nazewnictwie bardzo czesto są podążane, to już np. być może niektórzy pierwszy raz spotkają się z nieświadomym łamaniem reguły CA1819: Properties should not return arrays, co może mieć później przykre konsekwencje (odkrycie w takim przypadku dlaczego tak, a nie inaczej zachowuje się kod zapewniam jest dość czasochłonne).

Aktualną wersję programu można odnaleźć pod adresem http://www.microsoft.com/downloads/en/details.aspx?FamilyID=917023f6-d5b7-41bb-bbc0-411a7d66cf3c&displaylang=en. Dokładniej interesująca jest część System Requirements, która mówi o tym, że instalacja odbywa się pośrednio poprzez zainstalowanie Microsoft Windows SDK. Po zainstalowaniu tego pakietu, w katalogu należy odnaleźć instalator FxCop. Sam program w wersji Express nie jest zintegrowany z Visual Studio, co oznacza, że musimy dysponować zbuildowaną wersją aplikacji. Do analizy wybierany jest rodzaj pliku Microsoft .NET Assemblies, czyli .dll lub .exe. Po uruchomieniu analizy możemy cieszyć się masą błędów i ostrzeżeń, których analiza z pewnością zajmie sporo wolnego czasu. Pomimo tego jak dobrze widać, nawet nie przestrzegając wyszczególnionych reguł, korzystając z FxCop, a nie z funkcji statycznej analizy kodu uruchamianej na solucji w Visual Studio, projekt kompiluje się poprawnie.

Co do samej obsługi programu zainteresowani bez problemu znajdą odpowiednie tutoriale w Internecie. Ja ze swojej strony zamieszczam link do krótkiego wprowadzenia http://www.codeproject.com/KB/dotnet/FxCop.aspx.

Projekt bazodanowy w Visual Web Developer 2010 Express

 

Po nieco dłuższej niż przewidywałem przerwie czas powrócić do blogowania. W dalszym ciągu rozwijany będzie przeze mnie (a raczej przepisany na nowo) projekt o nazwie shoppingapp, który do tej pory był projektem konkursowym, aczkolwiek, który ze względu na jego ramy czasowe, nie został ukończony. Ba, można by nawet rzec, że na nowo się rozpoczął 🙂

Wróćmy na początek do niezałatwionych spraw. W jednym z postów sygnalizowałem (a może i nie) o kłopotach z podpięciem projektu bazodanowego do VS. Chodzi o wrzucenie bazy danych do przeznaczonego w tym celu katalogu App_Data, tworzonego domyślnie po utworzeniu solucji w Visual Web Developer 2010 Express. Sql w wersji 2008R2 Express. Niestety próba dodania nowego elementu do wspomnianego wcześniej katalogu kończyła się komunikatem

"Failed to generate a user instance of SQL Server due to a failure in starting the process for the user instance. The connection will be closed."

Dla osoby nie mającej wcześniej styczności z programowaniem z wykorzystaniem SQL Server i związaną z tym konfiguracją może to być nie lada zagadka. Gdy konkurs się rozpoczynał nie było czasu na rozwiązanie tego problemu, ale niezałatwionych spraw nie można pozostawić od tak sobie.

Dłuższa chwila google’owania pozwoliła oczywiście na znalezienie rozwiązania. Jedna z jego wersji znajduje się na stronie http://blog.krisvandermast.com/FailedToGenerateAUserInstanceOfSQLServerDueToAFailureInStartingTheProcessForTheUserInstanceTheConnectionWillBeClosed.aspx

Konfiguracji można również dokonać w oparciu o http://support.microsoft.com/kb/2002980 

Ponadto na serwerze należy się upewnić, że ustawiona została opcja user instances enabled. Sprawdzenia jej ustawienia można dokonać wykonując skrypt z komendą sp_configure, a zmiany za pomocą treści:

exec sp_configure 'user instances enabled', 1. 
go
reconfigure
 

Bardzo ciekawy jest punkt dotyczący usunięcia folderu SQL Server Data\SQLEXPRESS, jakie jest tego uzasadnienie przyznam nie wiem, ale najważniejsze że działa.

Po wykonaniu opisanych operacji można już tworzyć bez przeszkód elementy do katalogu App_Data. Osobiście preferowałbym jednak inne podejście, czyli utworzenie bazy danych za pomocą Management Studio i dołączenie jej do wspomnianego wyżej katalogu. Co więcej, możemy już dysponować przygotowaną bazą danych na serwerze. W takim przypadku VS zgłosi komunikat błędu przy próbie dodania pliku .mdf. Aby wyjść z tej sytuacji z twarzą należy w SSMS kliknąć na bazie prawym przyciskiem myszy i wybrać opcję Tasks->Detach w celu odłączenia bazy z serwera. Następnie po dodaniu pliku .mdf do projektu w Visual Studio można ponownie dołączyć bazę do serwera (prawym przyciskiem myszy na databases instancji –> Attach…).

WPF: Rysowanie 2D

 

Jak to mówią – nie samym konkursem człowiek żyje 🙂 Elementem niezbędnym do powstania mojej pracy magisterskiej jest wykorzystanie grafiki 3D, stąd zapadła decyzja, że powstanie ona w technologii WPF. Nie o grafice trójwymiarowej jednak w tym poście. Okazało się bowiem, że istnieje również potrzeba wykorzystania grafiki 2D do wyświetlania przebiegów funkcji.

Celem, który mi przyświeca od początku pisania aplikacji jest stworzenie architektury, która przynajmniej w jakimś  niewielkim stopniu, na miarę mojej wiedzy, mogłaby być później rozbudowywana. Przede wszystkim to dobry impuls do zastosowania wzorca projektowego obserwatora (Observer Pattern), w odmianie .Net–owej, czyli Event Pattern. Odmiana ta zakłada wykorzystanie zdarzeń oraz delegatów. Samego wzorca nie będę tutaj opisywał – zachęcam do zapoznania się z nim pod adresem: http://msdn.microsoft.com/en-us/library/ee817669.aspx.

Elementem składowym całej układanki jest utworzenie osobnej kontrolki umożliwiającej wyświetlanie rysunków na płaszczyźnie. Jako, że mam pewne doświadczenie w tworzeniu grafiki w Windows Forms, to z lekkim zaskoczeniem przekonałem się, że w WPF sposób tworzenia grafiki jest dość odmienny. Postanowiłem więc moje spostrzeżenia z tego etapu prac umieścić na blogu, aby później odnalezione przeze mnie informacje nie uległy zapomnieniu.

Zacznijmy od tego, że z racji tego, że moje główne okno aplikacji ma być zajęte wyświetlaniem grafiki 3D i innych rzeczy to chciałbym, aby dla grafiki 2D otwierane było nowe okno. W tym celu należy stworzyć nowy element typu Window, w którym umieszczona zostanie przygotowana wcześniej kontrolka. Nowe okno wywołać można  w następujący sposób:

var myWindow = new WindowPlainGraph();
myWindow.Show();

W powyższym kodzie WindowPlainGraph to nazwa pliku wybrana dla elementu typu Window.

Kontrolka została stworzona w tej samej przestrzenii nazw, stąd nie ma konieczności dodawania żadnych referencji do projektu. Przydałoby się jednak powiadomić w jakiś sposób edytor, że kontrolka taka będzie wykorzystywana w projekcie. W kodzie XAML na nowej stronie dla elementu Window, trzeba dodać następujący wpis:

xmlns:myPlainGraphControl="clr-namespace:MyNamespace"
    

W przypadku gdyby konieczne było dodanie kontrolki z zewnątrz projektu trzeba dodać jeszcze parametr assembly. Od tego momentu w kodzie XAML kontrolkę można wstawiać używając składni:

<myPlainGraphControl:PlainGraph/>

gdzie PlainGraph to klasa z kontrolką. Co warte podkreślenia, aby element z tego samego assembly mógł być dostępny w projekcie z kodu, konieczne jest nadanie mu identyfikatora x:Name, zamiast zwyczajnego Name.

Co do samego rysowania przebiegów funkcji, to przynajmniej w moim podejściu do narysowania jest zbiór odcinków wyznaczonych przez pary punktów. Koniec jednego odcinka oczywiście musi być zarazem początkiem drugiego odcinka. W przypadku WPF doskonale do tego celu nadaje się obiekt klasy Polyline. Punkty składowe tego obiektu ustawiane są za pomocą metody:

 polyline.Points.Add(...);

która jako argument przyjmuje obiekt typu Point, którego składowe z kolei są typu double. Sam obiekt Polyline wyświetlany jest na panelu poprzez dodanie go komendą:

pnlDrawing.Children.Add(polyline);

gdzie polyline to nazwa utworzonego obiektu typu Polyline.

Pamiętać należy bezwzględnie o tym, że żaden rysunek nie powstanie, jeśli nie zostanie do niego uprzednio przypisane “narzędzie rysujące”, np:

//Before adding to panel
polyline.Stroke = System.Windows.Media.Brushes.Black;
polyline.StrokeThickness = 2;

Właściwość zwracająca tablicę. Czy aby na pewno ?

 

Postawione w tytule posta pytanie jest jak najbardziej na miejscu. Już teraz mogę zdradzić tajemnicę, że nigdy tablicy nie udostępniamy w postaci właściwości.

Na początek chciałbym krótko zarysować problem. W czasie tworzenia kodu okazało się, że bardzo ułatwi mi zachowanie go czytelnym utworzenie klasy przechowującej tablice stworzonych przeze mnie obiektów. Wygląda to następująco:

public partial class DiagramPart
{
    private Signal2D[] firstSignal;
    private Signal2D[] secondSignal;
    ...
}
Klasa ta posiada wiele pól przechowujących tablice stworzonych wcześniej przeze mnie obiektów Signal2D, które w sobie zawierają również tablice z danymi. Chciałby móc operować na tych tablicach obiektów w dalszej części programu. Istnieje zatem naturalna pokusa, aby udostępnić je w następujący sposób:
 
...
public Signal2D[] FirstSignal
{
    get{ return this.firstSignal; }
}
...

W momencie utworzenia takiego kodu, przypomniało mi się, że gdzieś kiedyś czytałem, że nie należy udostępniać w ten sposób całej tablicy, a jedynie jej poszczególne elementy. Przystąpiłem więc do poszukiwań wspomnianego źródła, jednak okazały się one bezskuteczne, chociaż wiele poradników zalecało użycie w takim przypadku indeksatorów. Ale przecież w takim wypadku mogę się dostać jedynie do jednej tablicy obiektów, a ja posiadam ich wiele. W trakcie poszukiwań rozwiały się natomiast wszelkie wątpliwości co do postępowania przedstawionego w powyższym kodzie. Źródło: http://msdn.microsoft.com/en-us/library/0fss9skc.aspx wyraźnie mówi o tym, że: “PropertiesShouldNotReturnArrays”.

Podane w artykule sposoby rozwiązania problemu to: utworzenie ReadOnlyCollection, Collection lub wyglądające najprzyjaźniej utworzenie metody zamiast właściwości. Dwa pierwsze sposoby, przedstawione w zacytowanym artykule, pozwolę sobie pominąć ze względu na to, że nie chcę zastępować tablicy żadną kolekcją. W dalszej części skupię się zatem na rozwiązaniu trzecim, czyli utworzeniu metod, których odpowiedniki w Javie określane są jako getter i setter.

W przypadku chęci utworzenia metody, pozwalającej na uzyskanie obiektu, możliwe jest utworzenie zarówno metody zwracającej pojedynczy element, jak i całej tablicy. W odniesieniu do kodu zamieszczonego na początku posta wyglądałoby to następująco:

//Getter for one element
public Signal2D GetFirstSignals(int i)
{
    return firstSignal[i].Clone();
}

//Getter for whole array
public Signal2D[] GetFirstSignals()
{
    Signal2D[] result = new Signal2D[firstSignal.Length];

    for(int i = 0; i < result.Length; i++)
    {
        result[i] = firstSignal[i].Clone();
    }

    //Do not use Array.Clone() method !

    return result;
}

Ponieważ użytkownik może zmieniać pobrane elementy tablicy/tablice, należy przy zwracaniu obiektu utworzyć jego kopię. W tym celu napisana została dla klasy Signal2D metoda Clone(), której implementacji w tym miejscu nie będę przedstawiał. Przy zwracaniu pojedynczego obiektu wykorzystywany jest przekazany parametr metody, który służy do pobrania obiektu o odpowiednim indeksie i sklonowania go. W drugim przypadku, czyli w momencie zwracania całej tablicy, wykonywana jest identyczna operacja, ale umieszczona w pętli. Przestrzegam przed pokusą skorzystania w tym momencie z metody systemowej Clone() wywoływanej dla tablicy, która jednocześnie wymaga rzutowania. Po użyciu tej metody wykonywana jest tzw. shallow copy, co powoduje, że kopiowane są tylko referencje do obiektów, a nie same obiekty.

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.