Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Kompendium programisty VB.NET - nr 5 Bazy danych, odtwarzacze plików dźwiękowych, algorytmy wyszukiwania wzorca w tekście … chyba starczy na jeden numer. Bazy danych to nie tylko ACCESS, to także systemy oparte na serwerach SQL. Prosta aplikacja wykorzystująca darmową wersję serwera firmy Microsoft – MSDE, to ciekawa propozycja rozpoczęcia pracy z profesjonalnymi systemami bazodanowymi. Dla amatorów multimediów (o)krągły odtwarzacz plików WAV. W każdym numerze kompendium nie może zbraknąć algorytmiki- dziś Knuth, Morris, Pratt, Boyer, Mooore oraz Naiwny- znacie ich ? Obsługa baz danych w VS.NET MSDE MSDE 2000 to darmowa edycja bazy danych mająca zgodność z komercyjnym produktem firmy Microsoft SQL Server 2000. Wykorzystuje tę samą odmianę dialektu języka SQL - T-SQL. Zastosowanie MSDE to nie tylko wersje testowe dużych aplikacji bazodanowych opartych na serwerze SQL, lecz także główny sposób przechowywania danych w mniejszych aplikacjach opartych na bazie SQL. Zaletą tego systemu jest możliwość łatwej i bezproblemowej migracji danych do systemu serwerowego (Microsoft SQL Server 2000) w przypadku, gdy baza się rozrośnie i wymaga większej skalowalności. Zaletą MSDE jest jego dostępność z wieloma produktami firmy Microsoft, co znacznie ułatwia rozpowszechnianie aplikacji opartych na tym silniku baz danych. Posiadacze pakietu Office XP Professional mogą wykorzystywać aparat MSDE jako alternatywne narzędzie do przechowywania baz danych w stosunku do Microsoft Jet (silnik baz danych, na którym jest oparty system ACCESS). Komercyjnie MDSE mogą wykorzystywać w swoim oprogramowaniu posiadacze następujących produktów: - MSDN Universal. Enterprise i Professional - Microsoft Office XP Developer - SQL Server Developer, Standard, Enterprise - Microsoft Visual Studio.NET Architect, Developer, Professional SQL Server 2000 Desktop Engine (MSDE) - jest w 100% kompatybilny z pełną komercyjną wersją serwera. Pisząc programy do MSDE pisze się je w ten sam sposób jak dla wersji SQL Server. Jako wersja darmowa MSDE posiada pewne ograniczenia. Podstawowym jest górna granica wielkości bazy danych. Maksymalny łączny rozmiar pików mdf i ldf nie może przekroczyć 2 GB. Liczba baz nie jest ograniczona w stosunku do SQL serwera i możne wynosić maksymalnie 32 767. Drugim ograniczeniem jest limit jednocześnie przetwarzanych zapytań. Dokumentacja firmy Microsoft podaje, iż po przekroczeniu 5 jednoczesnych zapytań wydajność motoru bazy spada, w wyniku dokładania przez MSDE dodatkowych „pustych” operacji. Wynika stąd wniosek o mniejszej użyteczność tego narzędzia Algorytmika i programowanie 1 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs w wypadku obsługi większej liczby użytkowników. Dodatkowo należy pamiętać, iż MSDE jest przeznaczony głównie do przechowywania danych, stąd też jest pozbawiony narzędzi do zarządzania danymi. Standardowo można dokonywać operacji na danych oraz uprawnieniach za pomocą dostarczanego z MSDE narzędzia osql, pracującego w trybie konsolowym. MSDE pracujący na stacji roboczej jest aplikacją w małym stopniu absorbującą pamięć komputera, ponieważ została zaprogramowana jako program działający w tle. Pełna wersja SQL Server wymaga znacznie większych zasobów systemowych. Klasy VS.NET do obsługi i komunikacji z różnymi bazami danych VS.NET udostępnia wiele wbudowanych klas pozwalających na komunikację i obsługę baz danych. Niektóre tych z klas są specyficzne dla dostawcy (klasy dostawców), tzn. stanowią interfejs dla określonego typu systemu bazodanowego. Zadaniem ich jest połączenie z bazą danych, wykonywanie poleceń i odczytywanie danych. VS.NET wspomaga cztery odmiany dostawców baz danych: Microsoft SQL Server, OLE DB, ODBC oraz Oracle. W naszym kompendium omówimy współpracę z MS SQL Server. Należy pamiętać, że praca z innymi dostawcami będzie bardzo podobna, np. dla programistów wykorzystujących silnik Microsoft JET (czyli dane są zapisane w pliku ACCESS-a) przeznaczone będą klasy wspierające OLE DB zorganizowane w ten sam sposób co klasy SQL. Należy pamiętać, że wszystkie klasy wspomagające pracę z bazami danych znajdują się w przestrzeni nazw System.Data. Najważniejsze klasy dostawców w .NET to: Connection, Command, DataReader oraz DataAdapter. Ze względu na dostawców umieszczone są one w odpowiednich przestrzeniach nazw, np. SQLClient i OLEDB oraz zostały odpowiednio nazwane, np. SQLCommand. Klasa dostawcy Connection Command DataReader DataAdapter Przestrzeń nazw SQLClient SQLConection SQLCommand SQLDataReader SQLDataAdapter Przestrzeń nazw OleDb OleDbConnection OleDbCommand OleDbDataReader OleDbDataAdapter Klasa Connection powala nawiązać lub zakończyć połączenie z bazą danych. Do wykonania tej operacji jest potrzebny tzw. łańcuch połączeniowy (ConnectionString), czyli tekst zawierający najważniejsze informacje, pozwalające na połączenie z wybraną bazą danych udostępnioną przez wskazanego dostawcę. Łańcuch połączeniowy zawiera informacje w postaci par – klucz-wartość, specyficznych dla serwera baz danych, systemu zabezpieczeń. Przykładowe łańcuchy połączeń: - dla bazy obsługiwanej przez system SQL, gdzie system zabezpieczeń jest monitorowany przez serwer SQL (uwierzytelnianie SQL): Password=ala;Persist Security Info=True;User ID=sa;Initial Catalog=ksiazaki;Data Source=STACJA18 - dla bazy opartej na systemie Access (dostawca OleDb): Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Documents and Settings\Janusz\Moje dokumenty\AS.mdb;Persist Security Info=False Łańcuch połączeniowy można uzyskać w następujący sposób: 1. Otwieramy w dowolnym miejscu (np. na pulpicie) nowy plik o dowolnej nazwie i rozszerzeniu UDL, np. Nowy.UDL. 2. Otwieramy utworzony plik poprzez dwukrotne kliknięcie na nim. Pojawi się okno Właściwości łącza danych widoczne poniżej. Z listy Dostawcy OLE DB wybieramy dostawcę danych, od którego pochodzi nasza baza danych, np. Microsoft Ole DB Provider for SQL Serwer (jest to wybór SQL Servera lub silnika bazy MSDE). Algorytmika i programowanie 2 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs 3. Przechodzimy na kartę Połączenie. Wybieramy nazwę serwera, sposób uwierzytelniania oraz bazę na serwerze. Sprawdzamy, czy jest połączenie z bazą, wciskając przycisk Testuj połączenie. W przypadku pozytywnej odpowiedzi można zamknąć okno, klikając przycisk OK. Algorytmika i programowanie 3 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs 4. Otwieramy za pomocą Notatnika plik UDL. Tekst od słowa Provider zawiera łańcuch połączeniowy potrzebny do nawiązania połączenia z bazą ksiazki. W przypadku, gdy wykorzystujemy MS SQL Server lub MSDE w łańcuchu połączeniowym pomijamy klucz Provider. Połączenie z bazą polega na utworzeniu nowego obiektu klasy System.Data.SqlClient.SqlConnection zainicjowanego łańcuchem połączeniowym, a następnie wywołaniu metody Open dla tego obiektu, np.: Dim strPolacz As String strPolacz = "Password=ala;Persist Security Info=True;User ID=sa;Initial Catalog=ksiazaki;Data Source=STACJA18" objPolaczenie = New System.Data.SqlClient.SqlConnection(strPolacz) objPolaczenie.Open() Zamkniecie połączenia z bazą polega na wywołaniu metody Close, np.: objPolaczenie.Close() W trakcie działania aplikacji można sprawdzić, czy jest połączenie, wywołując właściwość State, np.: If objPolaczenie.State = ConnectionState.Open Then objPolaczenie.Close() End If Fragment kodu powyżej sprawdza, czy połączenie z bazą jest otwarte; w przypadku pozytywnej odpowiedzi zamyka zbędne już połączenie (należy pamiętać, że próba zamknięcia połączenia, gdy zostało ono już wcześniej zakończone, spowoduje wystąpienie wyjątku). Klasa Command pozwala wykonywać instrukcje SQL wraz z parametrami. Wykorzystujemy ją w celu, np. utworzenia bazy, tabeli, wpisu lub aktualizacji danych, wyszukania informacji z wykorzystaniem języka SQL. Do wykonania zapytań wykorzystujemy 3 metody ExecuteReader, ExecuteScalar oraz ExecuteNonQuery. Działanie tych metod jest następujące: - ExecuteReader –zwraca obiekt typu DataReader zawierający, np. zestaw rekordów będący wynikiem zapytania. - ExecuteScalar – zwraca zawartość pierwszej kolumny z pierwszego wiersza zestawu wyników, otrzymanego w wyniku zapytania. - ExecuteNonQuery wykonuje instrukcję SQL i zwraca ilość zmodyfikowanych wierszy. Przykładowe wykorzystanie klasy Command do usunięcia rekordu o wskazanym identyfikatorze może wyglądać następująco: strSQL = "delete from ksiazki" & " where id_k=3" Utworzenie zapytania SQL. Algorytmika i programowanie 4 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Dim objZapytanie As New System.Data.SqlClient.SqlCommand(strSQL, objPolaczenie) Utworzenie nowego obiektu typu System.Data.SqlClient.SqlCommand (klasa Command dla dostawcy SQL) i zainicjowanie go zapytaniem SQL oraz obiektem klasy Connection (zawierającym informacje o połączeniu z bazą danych). objZapytanie.ExecuteNonQuery() Wykonanie zapytania SQL. Klasa DataReader pozwala na tworzenie obiektów odczytujących strumień danych otrzymanych w wyniku zapytania. Obiekt typu DataReader posiada metody pozwalające na odczytanie kolejnych rekordów z bazy (tylko w przód) i nie ma możliwości modyfikacji danych w bazie. Przykładowe wykorzystanie klasy DataReader może wyglądać następująco: strSQL = "delete from ksiazki where id_k=3" Dim objZapytanie As New System.Data.SqlClient.SqlCommand(strSQL, objPolaczenie) Utworzenie zapytania SQL oraz nowego obiektu klasy SQLCommand. Dim objOdczyt As System.Data.SqlClient.SqlDataReader Utworzenie nowego obiektu DataReader. objOdczyt= objZapytanie.ExecuteReader() Wywołanie metody ExecuteReader (wykonanie zapytania SQL) i podstawienie wyniku wyszukiwań do obiektu typu DataReader. Do While objOdczyt.Read objOdczyt.GetInt32(0) objOdczyt.GetString(1) Loop Pobranie w pętli kolejnych rekordów i odczytywanie pierwszych dwóch pól bazy (o podanych typach). Klasa DataAdapter pozwala utworzyć obiekt pełniący kilka zadań: - pobiera i przechowuje podzbiór danych uzyskany w wyniku zapytania i zapisuje go w obiekcie typu DataSet. - umożliwia wykonywanie operacji na danych - umożliwia przesłanie zaktualizowanych danych do bazy Należy pamiętać, że obiekt DataAdapter pozwala na bezpołączeniowe operowanie na danych. Oznacza to, że dane pobrane z bazy są umieszczane w pamięci komputera i tam można wykonywać na nich różne operacje. Obiekt ten pozwala zwrócić informacje do bazy, dokonując jednocześnie ich aktualizacji. Do pobierania danych i umieszczania ich w obiekcie typu DataSet służy metoda Fill. Aktualizacja danych zapisanych w pamięci do bazy odbywa się za pomocą metody Update. Klasa DataSet pozwala tworzyć obiekty przechowujące dane w pamięci. Mogą one zawierać obiekty typu DataTable będące odpowiednikami danych z tabelami oraz pozwalają na definiowanie relacji za pomocą metody DataRelations. Klasa DataTable jest odpowiednikiem tabeli bazy danych, z tą różnicą, że dane przechowywane są w pamięci komputera. W klasie tej możemy wyróżnić kilka kolekcji pozwalających na uzyskanie informacji o danych, z których najważniejsze są DataRow i DataColumn. Pierwsza z nich Algorytmika i programowanie 5 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs pozwala na uzyskanie pojedynczego wiesza z obiektu DataTable, druga natomiast reprezentuje kolumnę w DataTable. Klasa DataView pozwala na sortowanie i filtrowanie danych umieszczonych w obiekcie typu dataTable. Przykład wykorzystania klasy DataAdapter może wyglądać następująco: Dim objTabela As DataTable Dim objWiersz As DataRow Deklaracja nowych obiektów typu DataTable i DataRow. Dim strSQL As String strSQL = "select * from ksiazki" Utworzenie ciągu znaków, stanowiącego zapytanie SQL. Dim objZapytanie As New System.Data.SqlClient.SqlDataAdapter(strSQL, objPolaczenie) Utworzenie nowego obiektu klasy DataAdapter i zainicjowanie go zapytaniem SQL oraz obiektem typu Connection) objDane = New DataSet("ksiazki") Utworzenie nowego obiektu DataSet o nazwie ksiazki. objZapytanie.Fill(objDane, "ksiazki") Wypełnienie obiektu typu DataSet danymi, uzyskanymi w wyniku wykonania zapytania. objTabela = objDane.Tables("ksiazki") Podstawienie pod obiekt typu DataTable tabeli ksiazki, znajdującej się w obiekcie typu DataSet. objWiersz = objTabela.Rows(wiersz) Pobranie jednego wiersza tabeli danych z obiektu typu DataTable i podstawienie go do obiektu typu DataRow (parametr wiersza określa numer rekordu z zestawu danych, zapisanych w pamięci). txtEID_k.Text = objWiersz.Item(0) txtEAutor.Text = objWiersz.Item(1) txtETytul.Text = objWiersz.Item(2) Odczyt kolejnych pól rekordu i umieszczenie pobranych danych w kontrolach typu TextBox. Kontrolka DataGrid pozwala wyświetlać dane w postaci tabeli umieszczonej na formularzu. Jest to najprostszy sposób wyświetlenia wyników zapytania w postaci tabelarycznej. Algorytmika i programowanie 6 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Do wypełnienia kontrolki DatGrid danymi wykorzystuje się wcześniej poznane klasy. Fragment kodu realizujący to zadanie może wyglądać następująco: Dim strSql As String strSql = "select * from ksiazki" Dim objZapytanie As New System.Data.SqlClient.SqlDataAdapter(strSql, objPolaczenie) Dim objDane As New DataSet("ksiazki") Dim objDaneWidok As DataView Dim objTabela As DataTable Utworzenie obiektów potrzebnych do pobrania i przeglądania danych. objZapytanie.Fill(objDane, "ksiazki") objTabela = objDane.Tables("ksiazki") Wypełnienie obiektu typu DataSet danymi, uzyskanymi w wyniku wykonania zapytania. objDaneWidok = objTabela.DefaultView Utworzenie nowego obiektu typu DataView określającego sposób wyświetlania danych. DataGrid1.DataSource = objDane DataGrid1.DataMember = "ksiazki" Przypisanie do kontrolki DataGrid zawartość obiektu typu DataSet (metoda DataSource). Ponieważ obiekt klasy DataSet może zawierać wiele tabel wskazanie poprzez właściwość DataMember, która z tabel będzie wyświetlana w kontrolce. Prosta aplikacja bazodanowa Książki oparta na aparacie MSDE W tym rozdziale zostanie omówiona prosta aplikacja bazodanowa pozwalająca na przeglądanie, edycje i wprowadzanie danych do bazy. Baza danych zostanie oparta na darmowej, okrojonej wersji SQL Server – MSDE. Interfejs aplikacji będą stanowiły cztery karty kontrolki TabControl. Algorytmika i programowanie 7 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Pierwszym krokiem w tworzeniu aplikacji będzie utworzenie bazy danych na serwerze MSDE. VS.NET udostępnia narzędzie – Server Explorer pozwalające na obsługę usług serwerowych oraz serwerów baz danych. W przypadku użycia MSDE jako silnika bazy danych dla aplikacji, zastosowanie Server Explorer ułatwia zarządzanie bazami na serwerze ze względu na brak narzędzi do zarządzania w okrojonej wersji SQL Servera. W celu utworzenia nowej bazy należy otworzyć okno Server Explorer i z listy serwerów wybrać serwer MSDE zainstalowany lokalnie na komputerze. Kliknięcie prawym przyciskiem myszy w nazwę serwera uaktywnia menu kontekstowe, z którego należy wybrać polecenie New Database. Algorytmika i programowanie 8 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs W oknie Create Database podajemy w polu New Database Name nazwę nowej bazy danych – ksiazki. Należy wybrać jeszcze sposób uwierzytelniania przy połączeniu z bazą. Do wyboru jest możliwość uwierzytelniania na poziomie wbudowanych zabezpieczeń systemu Windows lub system uwierzytelniania serwera SQL. W przypadku wyboru pierwszego sposobu weryfikacji uprawnień użytkownik, który będzie pracował z bazą, musi mieć uprawnienia do jej obsługi na poziomie systemu Windows. Wybieramy uwierzytelnianie na poziomie serwera SQL czyli opcję Use SQL Serwer Authentication jako sposób weryfikacji uprawnień. Jako Login Name wpiszemy nazwę użytkownika sa, oraz hasło ustawione podczas instalacji MSDE. Rozwijamy bazę danych ksiazki i wybieramy gałąź Tables, by utworzyć nową tabelę. Klikamy prawym przyciskiem myszy w gałąź Tables i z menu kontekstowego wybieramy polecenie New Table. Pojawi się okno, w którym należy zdefiniować pola bazy danych. Algorytmika i programowanie 9 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Definiujemy pola bazy (nazwa pola, typ, długość pola) wg rysunku poniżej. Baza zwiera 6 pól przechowujących informacje o książkach. Zapisanie nowo utworzonej tabeli nastąpi po wybraniu z menu kontekstowego polecenia Save Table. Algorytmika i programowanie 10 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Pojawi się okno dialogowe, do którego należy wpisać nazwę tabeli. Nowo utworzona tabela pojawia się jako obiekt w gałęzi Tables. Po rozwinięciu tabeli ksiazki pojawi się widok jej struktury. Algorytmika i programowanie 11 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Należy pamiętać, że Server Explorer pozwala nie tylko na tworzenie baz i tabel. Narzędzie to umożliwia modyfikację struktury tabel oraz wprowadzanie edycji i usuwanie danych. Kodowanie programu rozpoczniemy od utworzenia zmiennych globalnych oraz procedur umożliwiających połączenie z bazą i sprawdzenie ilości rekordów w bazie. W celu wykorzystania klas służących do obsługi baz danych, należy zaimportować przestrzeń nazw System.Data. Imports System.Data Deklaracja zmiennych globalnych wygląda następująco: Dim objPolaczenie As System.Data.SqlClient.SqlConnection Deklaracja obiektu typu SQLConnection, służącego do połączenia z bazą danych. Dim objDane As New DataSet("ksiazki") Deklaracja obiektu typu DataSet, służącego do przechowywania danych będących wynikiem zapytań SQL. Dim nr_rekordu As Integer = 0 Deklaracja zmiennej, w której będzie zapamiętany numer aktualnie przeglądanego rekordu. Dim objTabela As DataTable Deklaracja obiektu typu DataTable, służącego zapamiętania tabeli przechowywanej w obiekcie typu DataSet. Dim objWiersz As DataRow Deklaracja obiektu typu DataRow, pobierającego wiersz danych z tabeli przechowywanej w obiekcie typu DataTable. Algorytmika i programowanie 12 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Połączenie z bazą danych zostanie realizowane poprzez procedurę, która będzie wielokrotnie wywoływania w aplikacji. Private Sub polacz_z_baza() Dim strPolacz As String Deklaracja zmiennej typu String, w której zostanie zapamiętany łańcuch połączeniowy. strPolacz = "Password=janusz;Persist Security Info=True;User ID=sa;Initial Catalog=ksiazki;Data Source=(local)" Ustalenie wartości łańcucha połączeniowego (zostało to już opisane wcześniej). Try Rozpoczęcie bloku kodu chronionego. objPolaczenie = New System.Data.SqlClient.SqlConnection(strPolacz) Utworzenie nowego obiektu klasy SQLConnection. objPolaczenie.Open() Otwarcie połączenia z bazą. Catch ex As Exception MessageBox.Show("Bład w połączeniu z bazą danych", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End Sub Obsługa ewentualnych wyjątków, mogących wystąpić przy połączeniu z bazą. Aplikacja będzie informowała użytkownika o ilości rekordów w bazie. Informacja ta będzie także wykorzystywana podczas obsługi karty Edytuj dane. Funkcja ile_rekordow wykorzystuje poznane klasy do obliczenia ilości rekordów w tabeli ksiazki. Private Function ile_rekordow() As Integer Dim strSQL As String objDane.Clear() Deklaracja zmiennej typy String przechowującej zapytanie SQL oraz wyczyszczenie obiektu typu DataSet. strSQL = "select * from ksiazki" Zapamiętanie zapytania SQL, które będzie wykonane w funkcji. Dim objZapytanie As New System.Data.SqlClient.SqlDataAdapter(strSQL, objPolaczenie) Utworzenie nowego obiektu typu DataAdapter. If objPolaczenie.State = ConnectionState.Open Then Try Sprawdzenie, czy jest połączenie z bazą i rozpoczęcie bloku kodu chronionego. objZapytanie.Fill(objDane, "ksiazki") ksiazki. Wypełnienie obiektu typu DataSet (objDane) wynikiem zapytania SQL, wykonanego na tabeli Return objDane.Tables("ksiazki").Rows.Count Zwrócenie ilości rekordów w bazie (dokładnie ilości wierszy zapamiętanych w obiekcie typu DataSet w tabeli ksiazki. Algorytmika i programowanie 13 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Catch ex As DataException MessageBox.Show("Odczyt z bazy nie jest możliwy", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End If End Function Obsługa ewentualnych wyjątków, mogących wystąpić przy połączeniu z bazą i zakończenie kodu funkcji. Zakładka Dopisz dane umożliwia wprowadzanie nowych danych do tabeli ksiazki. W programie dane są wprowadzane do pól tekstowych, następnie sprawdzana jest ich poprawność. Wpis danych jest realizowany poprzez wykonanie zapytania SQL, służącego do wprowadzania danych. Procedura realizująca to zadanie może wyglądać następująco: Private Sub btnWpis_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnWpis.Click Dim strWpis As String If txtID_k.Text.Length = 0 OrElse Not IsNumeric(txtID_k.Text) Then MessageBox.Show("Proszę uzupełnić brakujace dane", "Brak danych", MessageBoxButtons.OK, MessageBoxIcon.Error) txtID_k.Focus() Exit Sub End If Sprawdzenie poprawności wprowadzonych danych do pola tekstowego. W przypadku błędnych danych (pole puste lub niewłaściwy typ danych, np. tekst) działanie procedury jest przerywane a kursor jest ustawiany w polu z błędem. If txtAutor.Text.Length = 0 Then MessageBox.Show("Proszę uzupełnić brakujace dane", "Brak danych", MessageBoxButtons.OK, MessageBoxIcon.Error) txtAutor.Focus() Exit Sub End If If txtTytul.Text.Length = 0 Then MessageBox.Show("Proszę uzupełnić brakujace dane", "Brak danych", MessageBoxButtons.OK, MessageBoxIcon.Error) txtTytul.Focus() Exit Sub End If Algorytmika i programowanie 14 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs If txtRok_wydania.Text.Length = 0 OrElse Not IsNumeric(txtRok_wydania.Text) Then MessageBox.Show("Proszę uzupełnić brakujace dane", "Brak danych", MessageBoxButtons.OK, MessageBoxIcon.Error) txtRok_wydania.Focus() Exit Sub End If If txtCena.Text.Length = 0 OrElse Not IsNumeric(txtCena.Text) Then MessageBox.Show("Proszę uzupełnić brakujace dane", "Brak danych", MessageBoxButtons.OK, MessageBoxIcon.Error) txtCena.Focus() Exit Sub End If If txtWydawnictwo.Text.Length = 0 Then MessageBox.Show("Proszę uzupełnić brakujace dane", "Brak danych", MessageBoxButtons.OK, MessageBoxIcon.Error) txtWydawnictwo.Focus() Exit Sub End If Sprawdzenie pozostałych pól formularza. strWpis = "insert into ksiazki(id_k,autor,tytul,rok_wydania," & _ "cena,wydawnictwo) values (" & txtID_k.Text & "," & "'" & txtAutor.Text & "'" & "," & "'" & txtTytul.Text & "'" & "," & txtRok_wydania.Text & "," & txtCena.Text & "," & "'" & txtWydawnictwo.Text & "'" & ")" Utworzenie zapytania SQL służącego do wprowadzania danych. polacz_z_baza() Połączenie z bazą. Dim objZapytanie As New System.Data.SqlClient.SqlCommand(strWpis, objPolaczenie) Utworzenie nowego obiektu typu SQLCommand. If objPolaczenie.State = ConnectionState.Open Then Try objZapytanie.ExecuteNonQuery() Sprawdzenie, czy jest połączenie z bazą i wykonanie w bloku kodu chronionego zapytania poprzez wywołanie metody ExecuteNonQuery. lblIleKsiazek.Text = "W bazie znajduje się " & ile_rekordow() & "książek" Aktualizacja informacji o ilości rekordów w bazie. Catch ex As Exception MessageBox.Show("Bład podczas wpisywania danych", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) Finally objPolaczenie.Close() End Try Obsługa ewentualnych wyjątków mogących wystąpić przy połączeniu z bazą i zakończenie połączenia z bazą w bloku Finally (w przypadku wystąpienia wyjątku połączenie na pewno zostanie zamknięte). czysc_wpis() MessageBox.Show("Dane zostały wpisane do bazy", "Wpis udany", MessageBoxButtons.OK, MessageBoxIcon.Asterisk) End If Algorytmika i programowanie 15 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs End Sub Wyczyszczenie pól tekstowym po wprowadzeniu danych oraz poinformowanie użytkownika o przeprowadzonej operacji. Karta przegląd danych wyświetla zwartość bazy w kontrolce typu DataGrid. Zawartość kontrolki jest zawsze aktualizowana przy zmianie karty w kontrolce TabControl. Wywołanie aktualizacji następuje podczas przejścia na odpowiednią kartę. Private Sub tclBaza_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles tclBaza.SelectedIndexChanged Procedura obsługi zdarzenia zmiany aktywnej karty w kontrolce typu TabControl.. If tclBaza.SelectedIndex = 1 Then czysc_wpis() End If W przypadku, gdy jest aktywna karta Dopisz dane (indeks 1), zostaje wywoływana procedura czyszczenia pół tekstowych na tej karcie. If tclBaza.SelectedIndex = 2 Then polacz_z_baza() przeglad_bazy() End If W przypadku, gdy jest aktywna karta Przegląd danych (indeks 2), zostaje nawiązywane połączenie z bazą i wywoływana procedura wypełniająca kontrolkę typu DataGrid danymi. If tclBaza.SelectedIndex = 3 Then nr_rekordu = 0 polacz_z_baza() odczyt_do_edycji(0) End If End Sub Algorytmika i programowanie 16 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs W przypadku, gdy jest aktywna karta Edytuj dane (indeks 3) zostaje nawiązywane połączenie z bazą i wywoływana procedura wyświetlająca pierwszy rekord bazy. Wyświetlanie danych w kontrolce typu DataGrid jest realizowane w następującej procedurze. Private Sub przeglad_bazy() Dim strSql As String strSql = "select * from ksiazki" Utworzenie zapytania SQL. Dim objZapytanie As New System.Data.SqlClient.SqlDataAdapter(strSql, objPolaczenie) Utworzenie nowego obiektu typu SQLDataAdapter. Dim objDane As New DataSet("ksiazki") Utworzenie nowego obiektu typu DataSet. Dim objDaneWidok As DataView Utworzenie nowego obiektu typu DataView. Dim objTabela As DataTable Utworzenie nowego obiektu typu DataTable. If objPolaczenie.State = ConnectionState.Open Then Try Sprawdzenie, czy jest połączenie z bazą danych i rozpoczęcie bloku kodu chronionego. objZapytanie.Fill(objDane, "ksiazki") Wypełnienie obiektu typu DataSet danymi będącymi wynikiem zapytania. objTabela = objDane.Tables("ksiazki") objDaneWidok = objTabela.DefaultView Zapamiętanie tabeli ksiazki w obiekcie typu DataTable i ustawienie sposobu sortowania danych. DataGrid1.DataSource = objDane DataGrid1.DataMember = "ksiazki" Wypełnienie kontrolki typu DataGrid danymi. Catch ex As DataException MessageBox.Show("Odczyt z bazy nie jest możliwy", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) Finally objPolaczenie.Close() End Try End If End Sub Obsługa ewentualnych wyjątków mogących wystąpić przy połączeniu z bazą i zakończenie połączenia z bazą w bloku Finally (w przypadku wystąpienia wyjątku połączenie na pewno zostanie zamknięte). Algorytmika i programowanie 17 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Karta Edytuj dane pozwala na przeglądanie pojedynczych rekordów. Użytkownik może zmodyfikować zwartość rekordu i zapisać zmiany oraz usunąć rekord. Funkcje te są realizowane poprzez odpowiednie zapytania SQL. Odczyt danych jest realizowany przez następującą procedurę: Private Sub odczyt_do_edycji(ByVal nr_rekordu As Integer) Dim strSQL As String strSQL = "select * from ksiazki" Dim objZapytanie As New System.Data.SqlClient.SqlDataAdapter(strSQL, objPolaczenie) objDane = New DataSet("ksiazki") If objPolaczenie.State = ConnectionState.Open Then Try objDane.Clear() objZapytanie.Fill(objDane, "ksiazki") Catch ex As System.Exception MessageBox.Show("Odczyt z bazy nie jest możliwy", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) Finally objPolaczenie.Close() End Try End If Zapamiętanie wszystkich rekordów bazy w obiekcie typu DataTable. If objDane.Tables("ksiazki").Rows.Count = 0 Then MessageBox.Show("Baza pusta- brak rekordów", "Baza ", MessageBoxButtons.OK, MessageBoxIcon.Warning) btnPoprzedni.Enabled = False btnNastepny.Enabled = False btnUsunRekord.Enabled = False btnEZapiszDane.Enabled = False wyswietl_rekord(-1) Exit Sub Sprawdzenie ilości rekordów w bazie. W przypadku, gdy baza jest pusta, poinformowanie użytkownika o tym fakcie i wyłączenie przycisków służących do operacji na danych. Wywołanie procedury wyswietl_rekord z wartością ujemną powoduje wyczyszczenie pól tekstowych. Algorytmika i programowanie 18 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Else btnNastepny.Enabled = True btnPoprzedni.Enabled = True btnUsunRekord.Enabled = True btnEZapiszDane.Enabled = True wyswietl_rekord(nr_rekordu) End If End Sub W przypadku gdy w bazie są dane włącznie przycisków służących do operacji na danych i wywołanie procedury wyswietl_rekord z numerem rekordu, który ma być aktualnie wyświetlony. Procedura wyswietl_rekord wyświetla jeden wiersz danych z obiektu typu DataTable. Private Sub wyswietl_rekord(ByVal wiersz As Integer) If wiersz >= 0 Then objTabela = objDane.Tables("ksiazki") objWiersz = objTabela.Rows(wiersz) txtEID_k.Text = objWiersz.Item(0) txtEAutor.Text = objWiersz.Item(1) txtETytul.Text = objWiersz.Item(2) txtERok_wydania.Text = objWiersz.Item(3) txtECena.Text = objWiersz.Item(4) txtEWydawnictwo.Text = objWiersz.Item(5) W przypadku, gdy w bazie są dane, do wyświetlenia zawartości pól wybranego rekordu wykorzystany jest obiekt typu DataRows. Else czysc_edycja() End If End Sub W przypadku, gdy baza jest pusta, wywoływana zostaje procedura czyszcząca pola tekstowe. Przyciski Następny i Poprzedni pozwalają na poruszanie się pomiędzy rekordami. Procedury zdarzenia z nimi związane zwiększają (lub zmniejszają) numer rekordu, jaki ma być wyświetlony i wywołują procedurę wyswietl_rekord. Dodatkowo informują użytkownika o osiągnięciu pierwszego lub ostatniego rekordu. Private Sub btnNastepny_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnNastepny.Click If nr_rekordu < objDane.Tables("ksiazki").Rows.Count - 1 Then nr_rekordu += 1 wyswietl_rekord(nr_rekordu) Else MessageBox.Show("Ostatni rekord - przejście dalej nie jest możliwe", "Uwaga", MessageBoxButtons.OK, MessageBoxIcon.Warning) End If End Sub Private Sub btnPoprzedni_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPoprzedni.Click If nr_rekordu > 0 Then nr_rekordu -= 1 wyswietl_rekord(nr_rekordu) Else MessageBox.Show("Pierwszy rekord - przejście dalej nie jest możliwe", „Uwaga", MessageBoxButtons.OK, MessageBoxIcon.Warning) End If Algorytmika i programowanie 19 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs End Sub Zapis zmian w danych następuje po kliknięciu przycisku Zapisz zmiany w rekordzie. Do modyfikacji rekordu jest wykorzystywane polecenie SQL w połączeniu z klasami VS.NET służącymi do obsługi baz danych. Procedura wypełniająca to zadanie może wyglądać następująco: Private Sub btnEZapiszDane_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEZapiszDane.Click Dim strWpis As String Dim strSQL As String If txtEID_k.Text.Length = 0 OrElse Not IsNumeric(txtEID_k.Text) Then MessageBox.Show("Błędny wpis", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) txtEID_k.Focus() Exit Sub End If Sprawdzenie poprawności wprowadzonych danych do pola tekstowego. W przypadku błędnych danych (pole puste lub niewłaściwy typ danych, np. tekst) działanie procedury jest przerywane a kursor zostaje ustawiony w polu z błędem. If txtEAutor.Text.Length = 0 Then MessageBox.Show("Błędny wpis", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) txtEAutor.Focus() Exit Sub End If If txtETytul.Text.Length = 0 Then MessageBox.Show("Błędny wpis", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) txtETytul.Focus() Exit Sub End If If txtERok_wydania.Text.Length = 0 OrElse Not IsNumeric(txtERok_wydania.Text) Then MessageBox.Show("Błędny wpis", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) txtERok_wydania.Focus() Exit Sub End If If txtECena.Text.Length = 0 OrElse Not IsNumeric(txtECena.Text) Then MessageBox.Show("Błędny wpis", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) txtECena.Focus() Exit Sub End If If txtEWydawnictwo.Text.Length = 0 Then MessageBox.Show("Błędny wpis", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) txtEWydawnictwo.Focus() Exit Sub End If Sprawdzenie pozostałych pól formularza polacz_z_baza() Połączenie z bazą danych strWpis = "id_k=" & txtEID_k.Text & ", autor='" & txtEAutor.Text & "',Tytul='" & _ Algorytmika i programowanie 20 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs txtETytul.Text & "', rok_wydania=" & txtERok_wydania.Text & ", cena=" & txtECena.Text & ", Wydawnictwo='" & _ txtEWydawnictwo.Text & "'" strSQL = "update ksiazki set " & strWpis & " where id_k=" & txtEID_k.Text Utworzenie zapytania SQL aktualizującego dane. If objPolaczenie.State = ConnectionState.Open Then Try Sprawdzenie, czy jest połączenie z bazą i otwarcie bloku kodu chronionego. Dim objZapytanie As New System.Data.SqlClient.SqlCommand(strSQL, objPolaczenie) objZapytanie.ExecuteNonQuery() Utworzenie nowego obiektu typu SQLCommand i wykonanie aktualizacji danych za pomocą zapytania SQL. odczyt_do_edycji(nr_rekordu) Aktualizacja danych na formularzu. MessageBox.Show("Rekord został zaktualizowany", "Informacja", MessageBoxButtons.OK, MessageBoxIcon.Information) Poinformowanie użytkownika o wykonaniu aktualizacji danych. Catch ex As DataException MessageBox.Show("Bład podczas aktualizacji danych", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) Finally objPolaczenie.Close() End Try End If End Sub Obsługa ewentualnych wyjątków mogących wystąpić przy połączeniu z bazą i zakończenie połączenia z bazą w bloku Finally (w przypadku wystąpienia wyjątku połączenie na pewno zostanie zamknięte). Ostatnim przyciskiem, który należy oprogramować jest Usuń rekord. Ponownie zostanie wykorzystane polecenie SQL służące do usuwania danych oraz klasy VS.NET. Private Sub btnUsunRekord_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnUsunRekord.Click polacz_z_baza() Połączenie z bazą danych. If objPolaczenie.State = ConnectionState.Open Then If ile_rekordow() > 0 Then Sprawdzenie, czy jest połączenie z bazą, i czy są rekordy w bazie. Dim Pytanie As DialogResult Pytanie = MessageBox.Show("Czy chcesz slasować bieżacy rekord?", "Usuwanie danych", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) If Pytanie = DialogResult.Yes Then Potwierdzenie usunięcia danych przez użytkownika. Dim strSQL As String strSQL = "delete from ksiazki" & " where id_k=" & txtEID_k.Text Algorytmika i programowanie 21 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Utworzenie zapytania SQL.. Try Dim objZapytanie As New System.Data.SqlClient.SqlCommand(strSQL, objPolaczenie) objZapytanie.ExecuteNonQuery() Utworzenie nowego obiektu typu SQLCommand i usunięcie rekordu za pomocą zapytania SQL. MessageBox.Show("Rekord został usunięty", "Informacja", MessageBoxButtons.OK, MessageBoxIcon.Information) lblIleKsiazek.Text = "W bazie znajdują się " & ile_rekordow() & "książek" odczyt_do_edycji(0) Potwierdzenie usunięcia rekordu oraz aktualizacji informacji o ilości rekordów w bazie. W formularzu zostanie wyświetlony pierwszy rekord. Catch ex As DataException MessageBox.Show("Bład podczas aktualizacji danych", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) Finally objPolaczenie.Close() End Try End If End If Else MessageBox.Show("Brak połaczenia z bazą", "Bład", MessageBoxButtons.OK, MessageBoxIcon.Error) End If End Sub Obsługa ewentualnych wyjątków mogących wystąpić przy połączeniu z bazą i zakończenie połączenia z bazą w bloku Finally (w przypadku wystąpienia wyjątku połączenie na pewno zostanie zamknięte). Przedstawiony powyżej przykład pokazuje podstawowe mechanizmy obsługi baz danych wykorzystywane w VS.NET. Wiele rzeczy można zrobić także inaczej, np. aktualizację danych wykonywać za pomocą obiektów klasy DataAdapter. Powyższy przykład miał pokazać, jak można połączyć efektywnie język SQL z klasami .NET Framework. Modyfikacja kształtu formularzy oraz biblioteka WinAPI Modyfikacja kształtu formularzy Formularz jest standardowym interfejsem aplikacji Windows. Domyślny kształt formularza to prostokąt lub kwadrat zawierający elementy charakterystyczne dla aplikacji - pasek aplikacji. Przyciski Zamknij, Minimalizuj czy Maksymalizuj. Istnieje jednak możliwość zmiany kształtu formularzy. Wykonanie tego zadania można opisać następująco: tworzymy nowy obiekt Region i przypisujemy go do właściwości Form.Region. Obiekt Region powinien zawierać definicję żądanego kształtu formularza. Do tego można wykorzystać klasę System.Drawing.Drawing2D.GraphicsPath, która pozwala zdefiniować obszar za pomocą różnych krzywych, elips kół czy prostokątów. Realizacja tego zadania może wyglądać następująco: Dim kolo As New System.Drawing.Drawing2D.GraphicsPath() Utworzenie nowego obiektu typu System.Drawing.Drawing2D.GraphicsPath. Algorytmika i programowanie 22 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs kolo.AddEllipse(100, 100, 300, 300) Zdefiniowanie obszaru – w tym przypadku koło o podanych współrzędnych. Należy pamiętać, że współrzędne koła to nie x, y środka, lecz górnego prawego narożnika obszaru, w którym znajduje się koło). Me.Region = New Region(kolo) Utworzenie nowego obiektu typu Region i przypisanie go do właściwości Region formularza. Należy pamiętać, że np. taka zmiana kształtu spowoduje, że nie będzie widać paska aplikacji oraz przycisków do obsługi okna. Skutkuje to brakiem możliwości przesunięcia czy zamknięcia formularza, stąd też należy to samodzielnie oprogramować. Przykładowy kod pozwalający na przesuwanie formularza zostanie przedstawiony w opisie aplikacji Odtwarzacza. Wykorzystanie biblioteki WinAPI do odtwarzania plików WAV. Biblioteki WinAPI pozwalają programistom na dostęp do wielu funkcji, których nie ma w narzędziach programistycznych. Dodatkowo nie zależą od języka programowania, co pozwala je wykorzystywać na różnych platformach stosowanych przez programistów. Środowisko .NET Framework nie posiada wbudowanych klas do obsługi plików audio. Do tego zdania można wykorzystać funkcje sndPlaySoundA zaszytą w bibliotece wimm.dll. Funkcja ta może odtwarzać dźwięk w następujących trybach: - synchronicznym (następuje przerwa w działaniu programu) - asynchronicznym (odtwarzanie jest wykonywane w tle). Wywołanie tej funkcji ma dwa parametry - nazwę odtwarzanego pliku i tryb odtwarzania pliku (synchroniczny lub asynchroniczny). Wykorzystanie funkcji WinAPI w VB.NET wymaga jej zadeklarowania ze wskazaniem biblioteki, z której pochodzi. Private Declare Function graj Lib "winmm.dll" Alias "sndPlaySoundA" (ByVal plik As String, ByVal flaga As Long) As Long Powyższy kod deklaruje nową funkcje w VB.NET o nazwie: graj. Tak naprawdę jest to funkcja sndPlaySoundA z biblioteki wimm.dll. Po takiej deklaracji wywołanie odtwarzania pliku WAV jest już bardzo proste. graj(PlikWAV, 1) Funkcja posiada dwa parametry – nazwę pliku oraz tryb odtwarzania dźwięku. Prosty odtwarzacz plików WAV Przedstawione powyżej wiadomości można wykorzystać w prostej aplikacji, służącej do odtwarzania plików audio. Interfejs aplikacji może wyglądać następująco. Algorytmika i programowanie 23 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Pierwszym krokiem jest zdefiniowanie procedury rysującej koło – obwódkę formularza. Działanie to będzie wykonywane w procedurze zdarzenia Paint formularza. Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint Dim pioro As New Pen(Color.Navy, 8) Zdefiniowanie koloru oraz grubości linii. Dim rysunek As System.Drawing.Graphics rysunek = Me.CreateGraphics Utworzenie nowego obiektu typu Graphics. rysunek.DrawEllipse(pioro, 100, 100, 300, 300) End Sub Narysowanie koła o podanych współrzędnych. Narysowany okrąg będzie obwódkę nowego formularza. Procedura zmiany kształtu zostanie wykonana podczas ładowania formularza (zdarzenie Load dla formularza). Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim kolo As New System.Drawing.Drawing2D.GraphicsPath() kolo.AddEllipse(100, 100, 300, 300) Me.Region = New Region(kolo) btnOdtwarzaj.Enabled = False btnStop.Enabled = False End Sub W kodzie procedury oprócz określenia nowego kształtu jest także deaktywacja przycisków. Nowy formularz nie ma paska aplikacji oraz przycisku Zamknij. Funkcje te należy samemu zaprogramować. Zamknięcie aplikacji jest bardzo proste. Private Sub btnKoniec_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnKoniec.Click Dim Pytanie As DialogResult Pytanie = MessageBox.Show("Czy zakończyc pracę odtwarzacza?", "Koniec pracy", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) If Pytanie = DialogResult.Yes Then Close() End If End Sub Do przesuwania formularza służą trzy procedury. Jednak konieczne jest zadeklarowanie zmiennych globalnych w aplikacji. Dim Przesun As Boolean Dim Punkt As Point Dim PlikWAV As String Pierwsze dwie zmienne służą do obsługi przemieszczania formularza, trzecia przechowuje nazwę otwartego pliku WAV. Wciśnięcie przycisku myszy na przycisku Przesuń wywołuje procedurę dla zdarzenia MouseDown tej kontrolki. Algorytmika i programowanie 24 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Private Sub btnPrzesun_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles btnPrzesun.MouseDown If e.Button = MouseButtons.Left Then Sprawdzenie czy zostal wciśnięty lewy przycisk myszki. Przesun = True Punkt = New Point(e.X, e.Y) Zmiana wartości zmiennej przesun (informacja, że formularz będzie przesuwany) oraz odczytanie współrzędnych myszki w momencie, gdy lewy przycisk jest wciśnięty. Else Przesun = False End If End Sub Zmiana wartości zmiennej przesun – informacja, że formularz nie będzie przemieszczany. Przesuwanie formularza jest realizowane dla zdarzenia MouseMove przycisku Przesuń. Procedura ta oblicza nowe współrzędne formularza i zmienia jego położenie poprzez właściwość Location. Private Sub btnPrzesun_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles btnPrzesun.MouseMove If Przesun Then Dim PunktNowy As Point PunktNowy = Me.PointToScreen(New Point(e.X, e.Y)) PunktNowy.Offset(-Punkt.X, Punkt.Y) Me.Location = PunktNowy End If End Sub Procedura dla zdarzenia MouseUp (przycisk myszy nie jest wciśnięty) ustala wartość zmiennej przesun na False (przenoszenie nie będzie wykonywane). Private Sub btnPrzesun_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles btnPrzesun.MouseUp Przesun = False End Sub Odtwarzanie pliku jest związane z jego wyborem. Zadanie to będzie realizowane poprzez kontrolkę OpenFileDialog dodaną w kodzie procedury. Procedura zdarzenia dla kliknięcia przycisku Otwórz WAV wygląda następująco: Private Sub btnOtworzPlik_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOtworzPlik.Click Dim dlgOtworz As New OpenFileDialog() Utworzenie nowego obiektu (kontrolki) typu OpenFileDialog. Dim sciezka() As String Deklaracja pustej tablicy służącej do zapamiętania ścieżki dostępu i nazwy pliku. dlgOtworz.Filter = "pliki WAV|*.wav" Utworzenie filtru dla okna dialogowego. If dlgOtworz.ShowDialog() = DialogResult.OK Then PlikWAV = dlgOtworz.FileName Wyświetlenie okna i odczyt nazwy wybranego pliku. Algorytmika i programowanie 25 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs sciezka = PlikWAV.Split("\") lblPlik.Text = sciezka(sciezka.Length - 1) Wyświetlenie w kontrolce typu Label nazwy wybranego pliku. btnOdtwarzaj.Enabled = True Włączenie przycisku Odtwarzaj. End If End Sub By aplikacja osiągnęła swoją funkcjonalność, brakuje jeszcze procedur zdarzenia dla przycisków Odtwarzaj i Stop. Wymaga to jednak zadeklarowania funkcji sndPlaySoundA. Private Declare Function graj Lib "winmm.dll" Alias "sndPlaySoundA" (ByVal plik As String, ByVal flaga As Long) As Long Procedura zdarzenia dla przycisku Odtwarzaj wygląda następująco: Private Sub btnOdtwarzaj_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOdtwarzaj.Click Try btnOtworzPlik.Enabled = False btnStop.Enabled = True Deaktywacja przycisku Otwórz WAV oraz włączenie przycisku Stop. graj(PlikWAV, 1) Wywołanie funkcji graj – plik WAV jest odtwarzany w trybie asynchronicznym (parametr 1 – tryb asynchroniczny, parametr 2- tryb synchroniczny). Catch ex As Exception MessageBox.Show("Bład przy odtworzeniu pliku", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End Sub Obsłużenie ewentualnych błędów mogących wystąpić podczas odczytu z pliku. Ostatnia procedura pozwala zatrzymać odtwarzanie pliku. Parametrem funkcji graj jest wartość 4 oznaczająca zatrzymanie odtwarzania pliku audio. Private Sub btnStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStop.Click graj("", 4) btnOtworzPlik.Enabled = True End Sub Przedstawiona powyżej aplikacja jeszcze nie dorasta do programów komercyjnych, jednak pokazuje, jak można modyfikować kształty formularzy oraz wykorzystywać WinAPI, zawierającą wiele użytecznych funkcji, niedostępnych na platformach programistycznych. Algorytmika i programowanie 26 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Algorytmy wyszukiwania wzorca w ciągu znaków Czym się zajmują komputery? Nie tylko obliczeniami, lecz także wyszukiwaniem danych. W dzisiejszym numerze kompendium w bloku poświęconym algorytmowi, troszkę o sposobach wyszukiwaniu wzorca w tekście. Oczywiście, jako tekst rozumiemy ciąg znaków w sensie informatycznym, który niekoniecznie musi mieć sens w kontekście treści. Biorąc pod uwagę ostatnie zdanie, tekstem może być także ciąg bitów, które przecież stanowią podstawę przy przetwarzaniu danych przez komputer. Istnieje wiele algorytmów wyszukiwania, stąd w bieżącym numerze musieliśmy dokonać pewnego wyboru. Zaczniemy od przedstawienia idei wyszukiwania w postaci algorytmu naiwnego. Jakie to będą algorytmy, o tym w dalszej części kompendium. Algorytm naiwny Wyszukiwanie wzorca w tekście. Problem ten dotyczy wielu aplikacji, np. najzwyklejszego edytora tekstu w momencie, gdy chcemy sprawdzić, czy dana fraza występuje we wprowadzonym łańcuchu znaków. Na początek wprowadźmy sobie dwa określenia: - tekst: ciąg znaków pewnego ustalonego alfabetu, który będziemy przeszukiwali o długości M - wzorzec: ciąg znaków, którego wystąpień będziemy wyszukiwali w tekście o długości N. Najprostszym rozwiązaniem problemu wyszukiwania wzorca w tekście jest tzw.algorytm naiwny. Polega on na porównywaniu kolejnych znaków tekstu ze wzorcem. Zaczynamy od pierwszego znaku tekstu. A C B C B A C F F C C A F A A B A B A TEKST WZORZEC W przypadku, gdy znaki są różne, przesuwamy wzorzec o jeden i ponownie sprawdzamy tekst. A B C B C C A F F C C A F A A B A B A TEKST WZORZEC A B B C C C F A C F C A F A A B A B A TEKST WZORZEC Przesuwając wzorzec, odnajdujemy miejsce, gdy pierwszy znak wzorca zgadza się ze znakiem tekstu. Wtedy sprawdzamy zgodność kolejnych elementów wzorca z elementami tekstu. A B B C C F C C A C F A F A A B A B A TEKST WZORZEC Na rysunku przedstawionym powyżej, występuje niezgodność drugiego znaku wzorca z elementem tekstu. Wyszukiwanie zaczynamy od początku, przesuwając wzorzec po raz kolejny o jedną pozycję. Algorytmika i programowanie 27 Kompendium wiedzy programisty VB.NET – nr 5 A B B C A B B C autor: Janusz Białowąs F C C A F A A B A B A TEKST C C A F WZORZEC Działanie to wykonujemy do momentu, gdy odszukamy wystąpienie wzorca: F C C C C A A F F A A B A B A TEKST WZORZEC lub gdy ostatni znak wzorca pokryje się z ostatnim znakiem tekstu (jednocześnie pierwszy znak wzorca będzie różny od odpowiadającego mu znaku w teście). Wydaje się to oczywiste, warto jednak pamiętać o tym, że kończymy wyszukiwanie dla indeksu znaku w tekście M-N, (chyba że znaki na tej pozycji są równe; wtedy należy sprawdzać ich zgodność dalej). A B B C F C C A F A A B C A C B A A F TEKST WZORZEC Praktyczna implementacja tego algorytmu jest bardzo prosta. Interfejs aplikacji może wyglądać następująco: Tekst jest odczytywany z pliku podczas ładowania aplikacji. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim NrPliku As String NrPliku = FreeFile() Try FileOpen(NrPliku, "..\tekst.txt", OpenMode.Input) tekst = LineInput(NrPliku) lblTekst.Text = tekst Catch ex As Exception MessageBox.Show("Bład podczas odczytu z pliku", "Błąd", MessageBoxButtons.OK, MessageBoxIcon.Error) Finally FileClose(NrPliku) End Try Algorytmika i programowanie 28 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs End Sub Praktyczna implementacja opisanego powyżej algorytmu wygląda następująco: Private Sub btnSprawdz_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSprawdz.Click Dim i, j As Integer i = 0 j = 0 Deklaracja zmiennych pomocniczych. Zmienna i służy do przechowywania indeksu znaku w tekście, od którego zaczynamy porównywanie, zmienna j służy do zliczania ilości zgodnych znaków we wzorcu i tekście. wzorzec = txtWzorzec.Text Pobranie wzorca z kontrolki typu TextBox i podstawienie pod zmienną Do While i <= tekst.Length - wzorzec.Length Rozpoczęcie pętli przesuwającej wzorzec co jeden znak w stosunku do tekstu Ostania pozycja indeksu w tekście ma wartość M-N. Do While j < wzorzec.Length AndAlso tekst.Chars(i + j) = wzorzec.Chars(j) j += 1 Loop W przypadku gdy znak wzorca i testu są równe, ma miejsce sprawdzanie kolejnych znaków i zliczanie ilości zgodnych wystąpień. Pętla kończy działanie, gdy porównywane znaki wzorca i tekstu są różne lub zliczona ilość zgodnych wystąpień osiągnie długość wzorca (oznacza to, że wzorzec został odnaleziony). If j = wzorzec.Length Then MessageBox.Show("Wzorzec występuje na pozycji " & i) End If Poinformowanie użytkownika o znalezieniu wzorca na pozycji i. j = 0 Wyzerowanie zmiennej zliczającej zgodne wystąpienia znaków we wzorcu i tekście. i += 1 Loop Przesunięcie indeksu, od którego rozpoczynamy porównywanie znaków o jeden i zakończenie bloku pętli. End Sub Opisany powyżej algorytm jest bardzo prosty w implementacji, lecz jednocześnie bardzo nieefektywny. Jego wadą jest to, że podczas wyszukiwania nie wykorzystuje się informacji zawartych we wzorcu, tzn. możliwości zwiększania przesunięcia wzorca o więcej niż o jeden znak. Algorytm K-M-P (Knuta, Morrisa, Ptatta) Algorym K-M-P wykorzystuje informacje zawarte we wzorcu, by zwiększyć przesunięcie podczas stwierdzenia niezgodności wzorca z tekstem na n - pozycji. Wracając do algorytmu naiwnego rozważmy sytuację następującą: Algorytmika i programowanie 29 Kompendium wiedzy programisty VB.NET – nr 5 A B B C F A A B B A A autor: Janusz Białowąs D B A A B A B A TEKST WZORZEC Stwierdzamy niezgodność wzorca z tekstem na 3. pozycji, po czym przesuwamy wzorzec o jeden, rozpoczynając ponownie porównywanie. A B B C F A B A A B D A A B A B A B A TEKST WZORZEC Znaki oznaczone zielonym tłem pokazują, do którego miejsca już sprawdziliśmy tekst, a teraz cofamy się o kilka pozycji i ponownie rozpoczynamy sprawdzanie. Algorytm K-M-P wykorzystuje informacje zawarte we wzorcu i pozwala zwiększyć przesuniecie, tak byśmy w niektórych przypadkach nie cofali się o tyle pozycji. Zwiększone przesunięcie jest widoczne na rysunku poniżej. A B B C F A B A A D B A A A B B A B A TEKST WZORZEC Zanim wyjaśnimy, jak działa algorytm K-M-P, trzeba wyjaśnić kilka pojęć. Prefiks łańcucha tekstowego o długości M znaków to K początkowych znaków tego łańcucha. Dla wartości k=0 prefiks nie zawiera żadnego znaku i jest to prefiks pusty. Jeśli K<M to jest to prefiks właściwy (czyli prefiks nie jest jednocześnie całym łańcuchem znakowym). W przypadku gdy K=M otrzymujemy prefiks niewłaściwy. Przykłady prefiksów dla łańcucha znaków ALE: „” prefiks pusty o długości K-0 „A” prefiks właściwy o długości K=1 „AL.” prefiks właściwy o długości K=2 „ALE” prefiks niewłaściwy o długości K=3 Sufiks łańcucha tekstowego o długości M znaków to K końcowych znaków tego łańcucha. Dla wartości k=0 prefiks nie zawiera żadnego znaku i jest to sufiks pusty. Jeśli K<M to jest to sufiks właściwy (czyli prefiks nie jest jednocześnie całym łańcuchem znakowym). W przypadku gdy K=M otrzymujemy sufiks niewłaściwy. Przykłady sufiksów dla łańcucha znaków ALE: „” sufiks pusty o długości K-0 „E” sufiks właściwy o długości K=1 „LE.” sufiks właściwy o długości K=2 „ELA” sufiks niewłaściwy o długości K=3 Brzegiem łańcucha znaków nazywamy jego prefiks i sufiks właściwy, w przypadku gdy prefiks i sufiks są sobie równe, np. dla tekstu ABAKAB jego brzeg jest zaznaczony pogrubioną czcionką ABAKAB. Dla tego łańcucha możemy określić maksymalną szerokość brzegu, czyli taką długość sufiksu i prefiksu, dla której te dwa podciągi są sobie równe. W tekście ABAKAB maksymalna szerokość brzegu wynosi 2. Algorytm K-M-P do obliczenia przesunięcia, o które można przesunąć wzorzec podczas wyszukiwania, wykorzystuje tablicę przechowującą informację o maksymalnych brzegach kolejnych Algorytmika i programowanie 30 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs prefiksów wzorca. Powstawanie takiej tablicy możemy zaobserwować na przykładzie poniżej. Wzorzec, który będziemy badali ma wartość: ABDCABDDAB. A 0 B D C A B D D A B Dla pierwszej komórki tablicy, gdzie prefiks ma długość 1 i wartość „A” szerokość brzegu wynosi 0. A 0 B 0 D C A B D D A B Dla drugiej komórki tablicy, gdzie prefiks ma długość 2 i wartość „AB” szerokość brzegu wynosi 0 (prefiks nie równa się sufiksowi). A 0 B 0 D 0 C A B D D A B Dla trzeciej komórki tablicy, gdzie prefiks ma długość 3 i wartość „ABD” szerokość brzegu wynosi 0. A 0 B 0 D 0 C 0 A B D D A B Dla czwartej komórki tablicy, gdzie prefiks ma długość 4 i wartość „ABDC” szerokość brzegu wynosi 0. A 0 B 0 D 0 C 0 A 0 B D D A B Dla piątej komórki tablicy, gdzie prefiks ma długość 5 i wartość „ABDCA” szerokość brzegu wynosi 1(prefiks A jest równy sufiksowi A- długość prefiksu i sufiksu jest równa 1, czyli maksymalnej szerokości brzegu). A 0 B 0 D 0 C 0 A 1 B D D A B Dla szóstej komórki tablicy, gdzie prefiks ma długość 6 i wartość „ABDCAB” szerokość brzegu wynosi 2 (prefiks AB jest równy sufiksowi AB długość prefiksu i sufiksu jest równa 2, czyli maksymalnej szerokości brzegu). A 0 B 0 D 0 C 0 A 1 B 2 D 3 D A B Dla siódmej komórki tablicy, gdzie prefiks ma długość 7 i wartość „ABDCABD” szerokość brzegu wynosi 3(prefiks ABD jest równy sufiksowi ABD - długość prefiksu i sufiksu jest równa 3, czyli maksymalnej szerokości brzegu). A 0 B 0 D 0 C 0 A 1 B 2 D 3 D 0 A B Dla ósmej komórki tablicy, gdzie prefiks ma długość 8 i wartość „ABDCABDD” szerokość brzegu wynosi 0. A 0 B 0 D 0 C 0 A 1 B 2 D 3 D 0 A 1 B Dla dziewiątej komórki tablicy, gdzie prefiks ma długość 9 i wartość „ABDCABDA” szerokość brzegu wynosi 3 (prefiks A jest równy sufiksowi A - długość prefiksu i sufiksu jest równa 1, czyli maksymalnej szerokości brzegu). Algorytmika i programowanie 31 Kompendium wiedzy programisty VB.NET – nr 5 A 0 B 0 D 0 C 0 A 1 B 2 D 3 D 0 A 1 B 2 autor: Janusz Białowąs Dla dziesiątej komórki tablicy, gdzie prefiks ma długość 10 i wartość „ABDCABDAB” szerokość brzegu wynosi 2. Zbudowanie tablicy przesunięć, widocznej powyżej, pozwala na zmianę wartości przesunięcia wzorca podczas przeszukiwania tekstu. Procedura wypełniająca tablice szerokością brzegu dla kolejnych fragmentów wzorca wygląda następująco: Private Sub przesuniecia(ByVal wzorzec As String) Dim i As Integer Dim szb As Integer = 0 Ustalenie początkowej wartości szerokości brzegu. ReDim tablicaP(wzorzec.Length) Ustalenie rozmiaru tablicy na podstawie długości wzorca (tablica została zadeklarowana jako tablica globalna o rozmiarze zerowym). tablicaP(0) = 0 Ustalenie szerokości brzegu (zerowej) dla pierwszego elementu tablicy. For i = 1 To wzorzec.Length – 1 Rozpoczęcie pętli sprawdzającej kolejne prefiksy wzorca. Do While szb > 0 And wzorzec.Chars(szb) <> wzorzec.Chars(i) szb = tablicaP(i) Loop If wzorzec.Chars(szb) = wzorzec.Chars(i) Then szb += 1 End If Obliczenie szerokości brzegu dla kolejnego prefiksu wzorca. tablicaP(i) = szb Zapisanie szerokości brzegu w tablicy przesunięć. Next End Sub Procedura przedstawiona powyżej jest wykorzystywana w wyszukiwaniu wzorca do ustalenia skoku, o jaki można przesunąć wzorzec, gdy część znaków tekstu jest zgodna z wzorcem. Wyszukiwanie wzorca jest wtedy modyfikacją algorytmu naiwnego. Private Sub btnSprawdz_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSprawdz.Click Dim i, j As Integer i = 1 j = 0 wzorzec = txtWzorzec.Text przesuniecia(wzorzec) Wypełnienie tablicy przesunięć Do While i <= tekst.Length - wzorzec.Length j = tablicaP(j) Do While j < wzorzec.Length AndAlso wzorzec.Chars(j) = tekst.Chars(i + j) j = j + 1 Algorytmika i programowanie 32 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Loop If j = wzorzec.Length Then MessageBox.Show("Wzorzec wystepuje na pozycji " & i, "Wynik wyszukiwań", MessageBoxButtons.OK, MessageBoxIcon.Information) End If i = i + tablicaP(j) + 1 Ustalenie wartości przesunięcia wzorca na bazie tablicy przesunięć (w przypadku gdy część znaków się pokrywa, jest możliwe większe przesuniecie wzorca). Loop End Sub Przedstawiony powyżej algorytm jest znacznie bardziej efektywny w porównaniu z algorytmem naiwnym, ze względu na możliwość zmiany wartości przesunięcia wzorca. Algorytm Boyera-Moore’a Kolejny algorytm wyszukiwania wzorca w tekście ponownie wykorzysta tablice przesunięć. Jednak sam proces wyszukiwania zacznie się inaczej. Algorytm ten sprawdza, czy ostatni znak wzorca jest zgodny z badanym znakiem w tekście. W przypadku gdy znak w tekście nie należy do wzorca, następuje skok o całą długość wzorca. Pokazuje to następujący przykład: A B B C D A A D A A D G A F G A F A A A S F A A D A A A D Ponieważ znak C nie występuje we wzorcu, możemy dokonać przesunięcia o długość wzorca (4). A B B C D A A D A A D G A F G A F A A A S F A A D A A A D Ponieważ znak D nie występuje we wzorcu, możemy dokonać przesunięcia o długość wzorca A B B C D A A D A A D G A F G A F A A A S F A A D A A A D W kolejnym skoku okazało się, że znak G w tekście występuje we wzorcu. W tym momencie następuje powolne przesuwanie wzorca i sprawdzanie, jak pokryją się znaki. A B B C D A A D A A D G A F G A F A A S F A A A D A A A D Powyższy algorytm możemy zapisać następująco: - jeżeli porównywany znak nie wchodzi w skład wzorca, możemy przesunąć wzorzec o całą jego długość. Algorytmika i programowanie 33 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs Jeżeli porównywany znak jest we wzorcu, algorytm przesuwa wzorzec, tak by znaki się pokryły i porównywanie jest kontynuowane. Tablica przesunięć będzie miała tyle pozycji, ile jest znaków w alfabecie. Dodatkowo wykorzystana będzie funkcja zwracająca numer znaku w alfabecie (z rozróżnieniem małych i dużych znaków; spacja będzie zwracała wartość 0). Funkcja zwracająca numer znaku wygląda następująco: Function indeks(ByVal znak As Char) As Integer If znak = " " Then Return 0 Else If znak.IsLower(znak) Then Return Asc(znak) - Asc("a") + 1 Else Return Asc(znak) - Asc("A") + 27 End If End If End Function Jak widać, funkcja sprawdza, czy znak wzorca to spacja, jeżeli tak, zwraca 0. Następnie sprawdza, czy znak to mała litera, oblicza jej numer, w przeciwnym przypadku oblicza numer dla dużej litery (dla uproszczenia polskie znaki są pominięte) Tablica przesunięć o rozmiarze alfabetu wykorzystuje funkcję indeks. Kod procedury tworzącej tablice przesunięć wygląda następująco: Sub przesuniecie(ByVal wzorzec As String) Dim i As Integer For i = 0 To 52 tablicaP(i) = wzorzec.Length - 1 Next Wypełnienie tablicy wartością długości wzorca For i = 0 To wzorzec.Length - 1 tablicaP(indeks(wzorzec.Chars(i))) = wzorzec.Length - i - 1 Next Ustalenie wartości przesunięcia dla znaków znajdujących się we wzorcu (jeżeli znak nie występuje we wzorcu, przesunięcie jest równe długości wzorca) End Sub Algorytm wyszukiwania jest realizowany przez następującą procedurę: Private Sub btnSprawdz_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSprawdz.Click Dim i, j, x As Integer wzorzec = txtWzorzec.Text i = wzorzec.Length j = wzorzec.Length Do While j > 0 j -= 1 i -= 1 Do While tekst.Chars(i) <> wzorzec.Chars(j) Algorytmika i programowanie 34 Kompendium wiedzy programisty VB.NET – nr 5 autor: Janusz Białowąs x = tablicaP(indeks(tekst.Chars(i))) If wzorzec.Length - j > x Then i = i + wzorzec.Length - j Else i = i + x End If If i >= tekst.Length Then MessageBox.Show("Brak wzorca w tekście ", "Wynik", MessageBoxButtons.OK, MessageBoxIcon.Information) Exit Sub End If j = wzorzec.Length - 1 Loop Loop MessageBox.Show("Wzorzec występuje na pozycji " & i, "Wynik", MessageBoxButtons.OK, MessageBoxIcon.Information) Jak widać, przedstawiony algorytm jest bardziej efektywny ze względu na to, iż rzadko jest realizowane przesunięcie o jeden. Algorytmika i programowanie 35