„WynajemSal” realizowany w trakcie drugiego semestru studiów
Transkrypt
„WynajemSal” realizowany w trakcie drugiego semestru studiów
Janusz Górczyński Projekt „WynajemSal” realizowany w trakcie drugiego semestru studiów podyplomowych WSZiM w Sochaczewie, 2011 Spis treści 1 WSTĘP .................................................................................................................... 3 2 BAZA DANYCH .................................................................................................... 4 2.1 2.2 3 TABELE ............................................................................................................. 4 PROCEDURY PRZECHOWYWANE ...................................................................... 11 APLIKACJA WINDOWSOWA ......................................................................... 15 3.1 UTWORZENIE PORJEKTU .................................................................................. 15 3.2 FORMULARZ GŁÓWNY APLIKACJI .................................................................... 17 3.3 MODUŁ OGÓLNY ............................................................................................. 20 3.4 PLIK KONFIGURACYJNY ................................................................................... 23 3.5 POTRZEBNE KLASY ZEWNĘTRZNE .................................................................... 26 3.5.1 CForStorageSub ......................................................................................... 26 3.5.2 CValidacja.................................................................................................. 27 3.5.3 CDataGridPrint.......................................................................................... 28 3.6 FORMULARZ FRMNOWASALA ......................................................................... 29 3.6.1 Klasa CAdministracja ................................................................................ 32 3.6.2 Uzupełnienie kodu klasy frmNowaSala ...................................................... 35 3.6.3 Uzupełnienie kodu formularza głównego ................................................... 36 3.6.4 Formularz frmNowaSala w „pracy” .......................................................... 37 2 1 Wstęp W trakcie zajęć drugiego semestru studiów podyplomowych na kierunku „Projektowanie baz danych” zrealizujemy od podstaw projekt, który otrzyma roboczą nazwę „WynajemSal”. W ramach realizacji tego projektu zostaną zrealizowane trzy główne jego komponenty obejmujące: 1) Zaprojektowanie bazy danych na MS SQL Server; 2) Utworzenie aplikacji VB.NET będącej interfejsem tej bazy danych; 3) Utworzenie aplikacji ASP.NET pełniącej rolę interfejsu komunikacyjnego dla potencjalnych klientów. Podstawą rozpoczęcia prac nad tym projektem jest przemyślenie funkcjonalności zamierzonego rozwiązania, a sam pomysł zajęcia się takim projektem wynika między innymi z dość dużego zainteresowaniem różnych podmiotów wynajęciem naszych sal dydaktycznych. W przypadku pomyślnej realizacji tego projektu cały proces udostępniania wolnych pomieszczeń ulegnie maksymalnemu uproszczeniu poprzez: • Umożliwienie via strona www zapoznanie się ze szczegółową ofertą uczelni w zakresie wynajęcia pomieszczeń; • Możliwość złożenia formalnego zamówienia na wynajem określonego typu sali (czy sal) poprzedzone uzyskaniem informacji o kosztach wynajęcia, sprawdzeniu dostępności i przy dalszym zainteresowaniu złożenia zamówienia; • Automatyczne wygenerowanie i dostarczenie w formie elektronicznej formalnej umowy finansowej (e-faktury); • Zarządzanie procesem wynajmowania pomieszczeń via intuicyjny interfejs. 3 2 Baza danych Baza danych zostanie utworzona na serwerze MS SQL Server 2008 (słuchacze zakładają ją indywidualnie dla siebie), na potrzeby tego opracowania będziemy się posługiwać nazwą bazy SaleWSZiM. W bazie zostaną utworzone tabele, procedury przechowywane i funkcje, diagram bazy i określone reguły związane z jej bezpieczeństwem. Przyjmiemy zasadę, że jedyna możliwość manipulowania danymi będzie się odbywała via procedury przechowywane. 2.1 Tabele Tabela RodzajSal będzie zawierać wykaz typów pomieszczeń, które znajdą się w ofercie wynajmu. Pole idtyp jest kluczem tej tabeli (automatyczna inkrementacja ze skokiem 1). Pole rodzaj zawiera skrótowy opis odnoszący się generalnie do liczby miejsc, a pole opis będzie wykorzystywane przy prezentacji zdjęć poszczególnych sal. Poniżej przykładowe dane zapisane w tej tabeli. Tabela Sale zawiera informacje o poszczególnych salach dydaktycznych, które mogą być wypożyczane. Kluczem tej tabeli jest pole idnr przechowujące unikalny numer sali (a więc klucz tabeli nie może być automatycznie inkrementowany) 4 Pole projektor informuje o tym, czy w sali jest zamontowany projektor podwieszany (wartość 1) czy też nie (wartość 0). Pole id_typ jest kluczem obcym (kluczem tabeli RodzajSal) i będzie wykorzystywane do ustanowienia relacji typu jeden-do-wielu. Poniżej widok przykładowych danych zapisanych w tabeli Sale. Tabela ZdjeciaSal została utworzona po to, aby można było dla danej sali dysponować więcej niż jednym jej zdjęciem. Pole foto będzie przechowywać nazwę zdjęcia danej sali, nazwa ta w połączeniu z nazwą katalogu stworzy pełną ścieżkę dostępu do zdjęć pomieszczeń, które będziemy prezentować potencjalnym klientom na stronie www. Pełna nazwa katalogu będzie pobierana z pliku konfiguracyjnego aplikacji (zarówno windowsowej jak i webowej). Przykładowe dane w tej tabeli pokazane są niżej. Tabela Stawki ma za zadanie przechowywanie informacji o kosztach wynajęcia poszczególnych rodzajów sal. Tabela jest tak zaprojektowana, aby była możliwość przechowywania informacji o historii wprowadzanych zmian stawek, zadanie to będzie realizować pole o nazwie DataZmiany typu smalldatetime. 5 Kluczem tej tabeli jest pole idc z automatyczną inkrementacją ze skokiem jeden. Pozostałe pola (z wyjątkiem DataZmiany) są typu money, ich odpowiednikiem po stronie VB.NET czy ASP.NET będzie typ decimal. Pole GodzinaKomputera przechowuje informacje o stawce godzinowej za jedno stanowisko komputerowe, podobnie pole GodzinaProjektora opisuje godzinny koszt udostępnienia projektora (nie dotyczy sal komputerowych). Pozostałe cztery pola przechowują informacje o kosztach wynajęcia określonego typu sali, jest to koszt dzienny (niezależnie od liczby osób i liczby godzin dziennie). Poniżej widok przykładowych danych zapisanych w tabeli Stawki. Tabela NaszeTerminy ma przechowywać terminarz zajęć własnych z podaniem rodzaju zajęć i (ewentualnie) wykazu sal zajętych. Generalnie chodzi o uniknięcie potencjalnych konfliktów. 6 Pole TerminSobota typu smalldatetime ma przechowywać sobotnią datę zjazdu sobotnio-niedzielnego, datę niedzielną ustalimy w odpowiedniej procedurze przechowywanej. Pole RodzajZajec przyjmie wartość 1 dla naszych statutowych studiów, wartość 2 dla studiów podyplomowych. W przypadku tych ostatnich pole SaleZajete będzie zawierać numery sal rozdzielone symbolem przecinka. Poniżej widok przykładowych danych zapisanych w tabeli NaszeTerminy. Tabela Klienci będzie przechowywać informacje o firmach wynajmujących nasze pomieszczenia. Projekt tej tabeli pokazany jest poniżej. W tabeli tej zapisano poniższe, absolutnie przypadkowe dane (na etapie projektowania aplikacji). 7 Tabela RezerwacjaKlienta ma do spełnienia w naszym projekcie bardzo ważną rolę, to w tej tabeli potencjalny klient będzie zapisywał swoje zapotrzebowanie na wynajęcie odpowiednich pomieszczeń dydaktycznych. Pole idk jest identyfikatorem klienta z tabeli Klienci, wykorzystamy je do nawiązania relacji między tabelą Klienci i RezerwacjaKlienta. Podobną rolę pełnią pola idnr oraz idnrBufet, które powiążą tę tabelę z tabelą Sale. Pole dataRejestracji będzie przechowywać datę zgłoszenia zapotrzebowania na wynajęcie sali. Wartość tego pola w połączeniu z wartością klucza (idr) tej tabeli zostanie wykorzystana do przygotowania formalnej umowy wynajęcia danej sali. 8 Pole rodzajSali wskazuje na rodzaj sali będącej przedmiotem wynajęcia (chodzi o typ sali dydaktycznej). Pole zakresDat typu tekstowego będzie przechowywać wszystkie daty wynajęcia rozdzielone symbolem przecinka. Jego uzupełnieniem jest pole liczbaDni, przechowujące informację o pełnej liczbie dni wynajęcia (tak naprawdę można z tego pola zrezygnować – dlaczego?). Pole dniTygodnia typu tekstowego reprezentuje ciąg siedmiu znaków, odpowiednio 1 lub 0, wskazujących na dany dzień tygodnia (od poniedziałku do niedzieli). Jeden oznacza wynajęcie danej sali w danym dniu. Pole liczbaGodzin będzie przechowywać informację o planowanej liczbie godzin zajęć, a pole liczbaKompow informację o liczbie potrzebnych stanowisk komputerowych. W przypadku wynajmowania innej sali niż komputerowa to pole przyjmuje wartość zero. Pola projektor i salaBufetowa przyjmują wartość jeden, jeżeli wynajmujący zgłasza potrzebę udostępnienia projektora czy sali bufetowej. Jeżeli nie, to pola te otrzymują wartość zero. Pola idnr oraz idnrBufet będą przechowywać numer sali przewidzianej do wynajęcia oraz numer sali bufetowej, jeżeli było takie zapotrzebowanie. Jeżeli nie, to pole to otrzyma wartość zero. Pola kosztNetto oraz kosztOstateczny zawierają odpowiednio koszt netto wyliczony zgodnie z przyjętym algorytmem i automatycznym rabatem wynikającym z wartości zamówienia oraz koszt netto po ostatecznym ustaleniu (przykładowo z rabatem dla stałego klienta). Na etapie składania zamówienia wartości obu pól są takie same, a ewentualna zmiana (zmniejszenie) pola kosztOstateczny może być przeprowadzona przez osobę zarządzającą wynajmowaniem sal. Pole dataZaplaty pozostaje puste do momentu wpłaty należności za zrealizowaną transakcję udostępnienia pomieszczeń dydaktycznych. Po wpływie należności na konto Uczelni pole to jest wypełniane przez osobę nadzorującą wynajem pomieszczeń, jednocześnie pole archiv otrzymuje wartość zero. Pola archiv przyjmuje wartość 1 dla oznaczenia tych pozycji, które jeszcze nie zostały zrealizowane, a wartość zero po zrealizowaniu zamówienia. W przypadku anulowania zamówienia pole to przyjmie wartość dwa. Przykładowe dane zapisane w tabeli RezerwacjaKlienta pokazane są niżej (tutaj bez pól kosztNetto, kosztOstateczny i dataZaplaty). 9 A tak wygląda pole liczbaDni z większą liczbą szczegółów. Być może, że w trakcie prac nad projektem będziemy musieli zmodyfikować proponowane tabele, dodać nowe itd., ale to wyjdzie w trakcie pracy. Na ten moment mamy utworzone tabele pokazane niżej na diagramie. 10 2.2 Procedury przechowywane Procedura pCzyDobryNip sprawdza, czy potencjalny klient webowy już był zarejestrowany w naszej bazie (dokładnie, czy istnieje rekord o takim Nip w tabeli Klienci). Jeżeli istnieje, to procedura zwraca jego identyfikator, jeżeli nie, to zwraca wartość zero. CREATE procedure [dbo].[pCzyDobryNip] @nip nvarchar(13), @idk int out as declare @ip as int select @ip=idk from dbo.Klienci where NIP=@nip set @idk=ISNULL(@ip,0) Procedura identyfikatorze. pDajKlientaWgId zwraca wszystkie dane klienta o podanym create procedure [dbo].[pDajKlientaWgId] @id int as select * from dbo.Klienci where idk=@id Procedura pDajDaneKlientow zwraca komplet informacji z tabeli Klienci sortując je po polu Nazwa. create procedure [dbo].[pDajDaneKlientow] as select * from dbo.Klienci order by Nazwa Procedura pWstawKlienta odpowiada za wstawienie do tabeli Klienci nowego rekordu z jednoczesnym zwróceniem jego identyfikatora. create procedure [dbo].[pWstawKlienta] @naz nvarchar(100),@adres nvarchar(100), @nip nvarchar(13), @mail nvarchar(50), @tel nvarchar(50), @osoba nvarchar(100), @idk int out as insert into dbo.Klienci (Nazwa, Adres, NIP, mail, telefon, OsobaKontaktowa) values (@naz, @adres, @nip, @mail, @tel, @osoba) set @idk=scope_identity() Procedura pUpdateKlienci odpowiada za modyfikację danych wskazanego parametrem @idk klienta. 11 create procedure [dbo].[pUpdateKlienci] @idk int, @nazwa nvarchar(100), @adres nvarchar(100), @nip nvarchar(13), @mail nvarchar(50), @telefon nvarchar(50), @osoba nvarchar(100) as update dbo.Klienci set Nazwa=@nazwa, Adres=@adres, NIP=@nip, mail=@mail, telefon=@telefon, OsobaKontaktowa=@osoba where idk=@idk Procedura pDajNaszeTerminy odpowiada za zwrócenie pełnej informacji o „naszych” terminach zajęć z podanego zakresu dat, ich rodzaju oraz ewentualny wykaz zajętych sal. Poza terminem sobotnim zwracane są daty o jeden dzień większe (czyli niedzielne). Jest to realizowane funkcją SQL dateadd(symbol_interwału, o_ile_więcej, termin_wyjściowy). create procedure [dbo].[pDajNaszeTerminy] @datap smalldatetime, @datak smalldatetime as select Terminsobota as Sobota, dateadd(dd,1,TerminSobota) as Niedziela, Rodzajzajec, SaleZajete from dbo.NaszeTerminy where TerminSobota >=@datap and dateadd(dd,1,TerminSobota) <=@dataK Procedura pDajStawki zwraca ostatni (najstarszy) rekord z tabeli Stawki. create procedure [dbo].[pDajStawki] as select top(1) GodzinaKomputera, SredniaSala, DuzaSala, Aula from dbo.Stawki order by DataZmiany desc GodzinaProjektora, MalaSala, Procedura pDajNumerySalWgTypu odpowiada za zwrócenie numerów tych sal, które odpowiadają przekazanemu parametrem @idt typowi sal. Lista zwróconych numerów możemy modyfikować parametrem @idpro. CREATE procedure [dbo].[pDajNumerySalWgTypu] @idt int, @idpro int as if @idpro=1 select idnr from dbo.Sale where id_typ=@idt and projektor=1 else select idnr from dbo.Sale where id_typ=@idt 12 Procedura pDajWykazTerminow zwraca pojedynczą wartość typu tekstowego zawierającą listę aktywnych dat rezerwacji sal określonego typu. CREATE procedure [dbo].[pDajWykazTerminow] @idnr nvarchar(100), @zakres nvarchar(1000) out as declare @xp as nvarchar(1000) select @xp=zakresDat from dbo.RezerwacjaKlikenta where archiv=1 and idnr in (@idnr) set @xp=ISNULL(@xp,'') Procedura pDajZakresDatDlaSali zwraca zawierającą wykaz dat aktywnych rezerwacji dla danej sali. pojedynczą wartość tekstową create procedure [dbo].[pDajZakresDatDlaSali] @idnr int, @zakres nvarchar(1000) out as select @zakres=zakresDat from dbo.RezerwacjaKlikenta where idnr=@idnr and archiv=1 Set @zakres=ISNULL(@zakres,'') Procedura pCzyWolnaDanaSala sprawdza, czy dana sala jest wolna. create procedure [dbo].[pCzyWolnaDanaSala] @idnr int, @TakNie int out as select @TakNie=COUNT(*) from dbo.RezerwacjaKlikenta where archiv=1 and idnr =@idnr Procedura pCzyWolnaSalaBufetowa sprawdza, czy sala danego typu nie została zarezerwowana jako sala bufetowa. create procedure [dbo].[pCzyWolnaSalaBufetowa] @idtyp int, @TakNie int out as select @TakNie=COUNT(*) from dbo.RezerwacjaKlikenta where archiv=1 and idnrBufet in ( select idnr from dbo.Sale where id_typ=@idtyp ) 13 Procedura pCzyWolnaSala odpowiada za sprawdzenie, czy jest wolna jakaś sala określonego typu. create procedure [dbo].[pCzyWolnaSala] @idtyp int, @idpro int, @TakNie int out as if @idpro=1 select @TakNie=COUNT(*) from dbo.RezerwacjaKlikenta where archiv=1 and idnr in ( select idnr from dbo.Sale where id_typ=@idtyp and projektor=@idpro ) else select @TakNie=COUNT(*) from dbo.RezerwacjaKlikenta where archiv=1 and idnr in ( select idnr from dbo.Sale where id_typ=@idtyp ) 14 3 Aplikacja windowsowa W tej części całego projektu zajmiemy się przygotowaniem aplikacji windowsowej, która umożliwi nam pełną obsługę i zarządzanie bazą daną omówioną w poprzednim rozdziale. Utworzymy nowy projekt aplikacji typu Windows Forms, z uwagi na złożoność projektu będzie to aplikacja z więcej niż jednym formularzem, w jej skład będą wchodziły niektóre z klas, które poznaliśmy w trakcie zajęć pierwszego semestru. Klasa CForStorageSub będzie przez nas wykorzystywana jako klasa bazowa, będziemy tworzyć na jej podstawie szereg specjalistycznych klas potomnych wykorzystywanych do realizacji konkretnych zadań projektowanej aplikacji. 3.1 Utworzenie porjektu Zaczynamy od uruchomienia VisualStudio.NET i wywołania polecenia New Project, co skutkuje otwarciem okna nowego projektu. Standardowo w oknie zainstalowanych szablonów powinien być wybrany szablon Windows Forms Application, jeżeli tak jest to przechodzimy do pola tekstowego Name i wpisujemy w nim nazwę tworzonej aplikacji. 15 W pokazanej sytuacji wpisana jest nazwa SpWynajemSal (zgodnie ze zmodyfikowaną notacją węgierską). Po akceptacji przycisku OK tworzone jest nowe rozwiązanie o podanej przez nas nazwie. Po krótkiej chwili nowy projekt zostanie utworzony, na ten moment zawiera on jedynie jeden formularz o domyślnej nazwie Form1. Po utworzeniu projektu powinniśmy zapisać go na dysku, wystarczy w tym celu wywołać polecenie Save All z menu File. W oknie dialogowym tego polecenia wskazujemy folder docelowy (korzystamy z przycisku Browse) i przyciskiem Save zapisujemy projekt, 16 3.2 Formularz główny aplikacji Jak wcześniej zostało powiedziane będziemy tworzyć projekt wielo formularzowy, tym samym musimy do projektu wprowadzić specjalny formularz, który będzie kontenerem (pojemnikiem) dla innych formularzy. Można także przekształcić istniejący formularz w formularz typu MDI (ang. Multi Document Interface), wystarczy w tym celu zmienić jego właściwość IsMdiContainer z domyślnej False na True, tak jak to pokazano niżej. Korzystając z okienka Solution Explorer zmieniamy jeszcze nazwę naszego formularza z dotychczasowej Form1.vb na frmMdiForm.vb. Zmiany tej możemy dokonać z menu kontekstowego uruchamianego prawym przyciskiem myszy po wskazaniu obiektu formularza, wystarczy wywołać polecenie Rename z tego menu. Kolejnym krokiem jest zmiana właściwości Name tego formularza z dotychczasowej Form1 na frmMdiForm. Robimy to w oknie właściwości tego formularza (po lewej sytuacja przed zmianą, po prawej po zmianie). Dla przypomnienia – system rozpoznaje każdy obiekt po jego nazwie. 17 Jedną z istotnych właściwości formularza typu MDI jest możliwość zbudowania menu użytkownika. Robimy to poprzez dodanie do projektu formularza specjalnej kontrolki (formantu) o nazwie MenuStrip, znajdziemy ją w przyborniku w grupie Menus & Toolbars. Kontrolkę tę umieszczamy w naszym formularzu, ale po zwolnieniu myszy zostaje ona umieszczona nie w samym formularzu, lecz na specjalnym pasku, który nosi nazwę tzw. tacy. Poniżej widok VS po wprowadzonych zmianach. Widzimy egzemplarz formantu MenuStrip z nazwą domyślną MenuStrip1 (nie ma potrzeby jej zmiany), w formularzu pod wierszem tytułowym pojawił się pasek menu, będziemy w nim w trybie graficznym definiować poszczególne pozycje menu. Na tym etapie utworzymy pozycję Administracja z poleceniami jak niżej: - Typy sal (podpolecenia: Nowy typ, Edycja, Usunięcie); - Sale (podpolecenia: Nowa sala, Edycja, Usunięcie); - Zdjęcia sal (podpolecenia: Dodanie, Aktualizacja, Uusnięcie, Przegląd); - Stawki (podpole cnie: Nowe stawki, Edycja, Usunięcie) - Separator - Zakończenie 18 Poniżej widok okna projektowania w trakcie tworzenia menu Administracja. W analogiczny sposób dodajemy pozostałe pozycje menu, przy czym korzystając z menu kontekstowego możemy wstawić taką pozycję jak separator. Przed uruchomieniem naszej aplikacji możemy jeszcze zmienić dwie własności formularza frmMdiForm, jedna z nich to właściwość Text (tytuł formularza), a druga to WindowState, która określa sposób otwarcia formularza. Poniżej zmienione wartości obu właściwości. Możemy już uruchomić nasz projekt, wystarczy w tym celu nacisnąć klawisz F5, poniżej pokazany jest (zminiaturyzowany) widok okna tej aplikacji z rozwiniętym menu Administracja. 19 Na tym etapie aplikacja nie reaguje na klik poszczególnych pozycji menu, jest to spowodowane tym, że jeszcze nie zostały napisane odpowiednie procedury zdarzeniowe reagujące na klik danego polecenia. 3.3 Moduł ogólny W każdej poważniejszej aplikacji składającej się z wielu formularzy, raportów i klas zachodzi potrzeba zadeklarowania stałych, zmiennych i innych obiektów (struktury, wyliczenia) w taki sposób, aby były dostępne dla wszystkich innych modułów tworzonej aplikacji. Podobne oczekiwania można także sformułować odnośnie procedur i funkcji, jeżeli mają być dostępne z innych modułów, to muszą być zadeklarowane w specjalnym module (pliku) typu Module. Dodamy taki obiekt do naszego projektu, a postępowanie jest standardowe: w oknie Solution Explorer klikamy prawym przyciskiem myszy nazwę naszego rozwiązania, z menu kontekstowego wybieramy polecenie Add i dalej New Item… . 20 W otwartym oknie Add New Item wskazujemy obiekt Module, w polu Name wpisujemy jego nazwę i przyciskiem Add dodajemy obiekt do naszego rozwiązania. W kodzie klasy obiektu ModulWspolny możemy teraz zadeklarować potrzebne zmienne, stałe, struktury, utworzyć publiczne i prywatne procedury i funkcje. Poniże kod tych deklaracji, w miarę dalszych prac można oczekiwać, że będziemy dodawać albo nowe zmienne, albo nowe procedury i funkcje do tego modułu. Jako pierwsze deklarowane są takie zmienne jak strTytul (tu będzie tytuł naszej aplikacji), strBaza (łańcuch połączenia do bazy SQL), Kwestor (nazwisko i imię osoby odpowiedzialnej za wystawienie faktury), stawkaVAT (aktualnie obowiązująca stawka VAT na usługi wynajmu pomieszczeń), bSqlOK (zmienna wskaźnikowa, wartość True wtedy, gdy z pliku konfiguracyjnego zostaną pobrane potrzebne informacje). Module ModulWspolny Public strTytul, strBaza, Kwestor As String, stawkaVAT As Decimal, bSqlOK As Boolean ' tablica Cena() będzie przechowywać stawki za wynajem Public Cena(5) As Decimal ' struktura OpisSal definiuje zmienną użytkownika, która w zwięzły ' sposób ma opisywać oczekiwania osoby zainteresowanej wynajęciem Public Structure OpisSal Dim rodzajSali As Integer Dim zakresDat As String 21 Dim dniTygodnia As String Dim liczbaDni As Integer Dim liczbaGodzin As Integer Dim liczbaStanowiskKomputerowych As Integer Dim plusProjektor As Integer ' 1 = tak, 0 -nie Dim plusBufet As Integer ' 1 = tak, 0 = nie Dim WolnaSala As Integer Dim WolnaSalaBufetowa As Integer End Structure ' Publiczna funkcja UstalGrb ma za zadanie ustalenie, który z ' formantów typu RadioButton w kontenerze GroupBox jest zaznaczony. ' warunkiem poprawnego funkcjonowania jest zapisanie zwracanej ' wartości w nazwie każdego z radiobuttonów. ' dopuszczalna numeracja od 0 do 9 Public Function UstalGrb(ByVal grb As GroupBox) As Integer Dim rdb As RadioButton For Each rdb In grb.Controls If rdb.Checked Then Try Return CInt(rdb.Name.Substring(rdb.Name.Length - 1, 1)) Catch ex As Exception Return 0 End Try End If Next Return 0 End Function ' prywatna funkcja wykorzystywana w innych procedurach tego modułu Private Function WymienZnakNaPozycji(ByVal tekst As String, _ ByVal pozycja As Integer, ByVal znak As String) As String Select Case pozycja Case 1 Return znak & tekst.Substring(1, tekst.Length - 1) Case tekst.Length Return tekst.Substring(0, tekst.Length - 1) & znak Case Else Return tekst.Substring(0, pozycja - 1) & znak & _ tekst.Substring(pozycja, tekst.Length - pozycja) End Select End Function ' Publiczna funkcja UstalGrbVCheckBox ma za zadanie ustalenie, które ' z formantów typu CheckBox w kontenerze GroupBox są zaznaczone. 22 ' Funkcja zwraca ciąg znaków zer i jedynek o liczbie znaków równej ' liczbie ChceckBoxow w kontenerze, symbol 1 oznacza zaznaczenie. ' Funkcja pobiera numer formantu z jego nazwy, numer występuje po ' znaku podkreślenia. Public Function UstalGrbCheckBox(ByVal grb As GroupBox) As String Dim chb As CheckBox, x As Integer, tp As String tp = RepeatString("0", grb.Controls.Count) Dim tw() As String For Each chb In grb.Controls If chb.Checked Then Try tw = chb.Name.Split("_") x = CInt(tw(1)) tp = WymienZnakNaPozycji(tp, x, "1") Catch ex As Exception Return "Błąd" End Try End If Next Return tp End Function ' prywatna funkcja wykorzystywana w innych procedurach tego modułu Private Function RepeatString(ByVal znak As Char, _ ByVal ile As Integer) As String Dim i As Integer, t As String = "" For i = 1 To ile t &= znak Next Return t End Function End Module 3.4 Plik konfiguracyjny W środowisku VS.NET istnieje możliwości przechowywania wybranych informacji w specjalnym pliku XML o nazwie zastrzeżonej app.config. Po kompilacji plik ten umieszczany jest w tym samym folderze co plik wykonywalny aplikacji pod nazwą taką jak nazwa rozwiązania uzupełnioną o fragment „,exe.config” (w przykładzie omawianym w tym opracowaniu będzie to plik o nazwie SpWynajemSal.exe.config). Plik ten może być swobodnie modyfikowany w dowolnym edytorze XML, a także w np. Notatniku. Wiele wierszy tego pliku nie może być zmienione, nas będzie interesował fragment między znacznikami <appSettings> i </appSettings> . 23 <?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <sources> <!-- This section defines the logging configuration for My.Application.Log --> <source name="DefaultSource" switchName="DefaultSwitch"> <listeners> <add name="FileLog"/> <!-- Uncomment the below section to write to the Application Event Log --> <!--<add name="EventLog"/>--> </listeners> </source> </sources> <switches> <add name="DefaultSwitch" value="Information" /> </switches> <sharedListeners> <add name="FileLog" type="Microsoft.VisualBasic.Logging.FileLogTraceListener, Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" initializeData="FileLogWriter"/> <!-- Uncomment the below section and replace APPLICATION_NAME with the name of your application to write to the Application Event Log --> <!--<add name="EventLog" type="System.Diagnostics.EventLogTraceListener" initializeData="APPLICATION_NAME"/> --> </sharedListeners> </system.diagnostics> <!—to jest fragment dla nas --> <appSettings> <add key="Tytul" value="System obsługi wynajmu sal WSZiM w Sochaczewie"/> <add key="BazaSQL" value="DataSource=BOSS-TECRA\JG_SQLSERVER;Initial Catalog=SaleWSZiM;Integrated Security=True"/> <add key="Vat" value="0,23"/> <add key="Kwestor" value="Maria Kowalska"/> </appSettings> </configuration> 24 Plik wzorcowy app.config można dodać do naszego rozwiązania pobierając go z folderu PomocniczePliki, przy czym postępujemy podobnie jak przy dodawaniu innych obiektów – różnica polega jedynie na tym, że będziemy wywoływać polecenie Existing Item zamiast New Item. Po dodaniu pliku do rozwiązania możemy zmodyfikować potrzebny fragment z poziomu VisualStudio (wystarczy zmiana nazwy serwera SQL oraz nazwy bazy danych na tym serwerze). Plik app.config musi być odczytany w momencie startu aplikacji, czyli w momencie otwierania formularza głównego frmMdiForm. Zadanie to zrealizujemy w procedurze obsługującej zdarzenie Load tego formularza. Odczytanie pliku konfiguracyjnego wymaga zaimportowania niżej pokazanych przestrzeni nazw. Pobrane informacje są przypisywane do odpowiednich zmiennych globalnych (zadeklarowanych w ModulWspolny), Musimy także dodać odpowiednią referencję do biblioteki System.configuration (we właściwościach projektu w zakładce References). Imports System Imports System.Configuration Imports System.Configuration.ConfigurationSettings Public Class frmMdiForm Private Sub frmMDI_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load Try strTytul =ConfigurationManager.AppSettings("Tytul") strBaza = ConfigurationManager.AppSettings("BazaSQL") StawkaVAT = ConfigurationManager.AppSettings("Vat") Kwestor = ConfigurationManager.AppSettings("Kwestor") bSqlOK = True Catch ex As Exception MsgBox("Mam problem z odczytaniem pliku konfiguracyjnego", _ MsgBoxStyle.Critical, "Uruchomienie formularza MDI") End Try Me.Text = strTytul End Sub ' dalsze instrukcje w tej klasie Rnd Class 25 3.5 Potrzebne klasy zewnętrzne W tworzonej aplikacji możemy wykorzystać kilka interesujących klas, które poznaliśmy w semestrze pierwszym. Klasy te możemy włączyć do naszego rozwiązania w postaci odpowiedniego pliku, inna możliwość to utworzenie biblioteki DLL (ang. Dynamic link library) i wskazanie we właściwościach projektu lokalizacji takiej biblioteki. Dla treningu zastosujemy pierwsze rozwiązanie do klasy CForSorageSub, a drugie do dwóch pozostałych klas. 3.5.1 CForStorageSub W folderze przeznaczonym dla przechowywania plików demonstracyjnych tego projektu znajduje się plik CForStorageSub.vb, który zawiera pełny kod tej klasy. Zaimportujemy teraz ten plik do naszego projektu (dokładniej: rozwiązania). Robimy to analogicznie jak w przypadku pliku konfiguracyjnego: 1) Klik prawym przyciskiem myszy na nazwę rozwiązania w oknie Solution Explerer; 2) Z menu kontekstowego wybieramy polecenie Add i dalej Existing Item; 3) W oknie Add Exiting Item wskazujemy potrzebny plik (wcześniej otwierając stosowny folder); 4) Klik przycisku Add importuje wskazany plik i włącza go do naszego rozwiązania. Widok okna Solution Explorer po zaimportowaniu tego pliku pokazany jest obok. 26 3.5.2 CValidacja W przypadku klasy CValidacja postąpimy inaczej, skorzystamy z faktu, że dysponujemy wersją DLL tej klasy, a więc wskażemy we właściwościach rozwiązania referencję do tej biblioteki. Wykonujemy kolejne kroki: 1) Otwieramy okno właściwości rozwiązania (klik odpowiedniej ikony w oknie Solution Explorer lub z menu kontekstowego lub z menu głównego); 2) Przechodzimy do zakładki Referenses; 3) Poprzez klik przycisku Add otwieramy okno dodawania referencji; 4) W oknie Add Reference wskazujemy potrzebny plik biblioteki DLL; 5) Klik przycisku OK dodaje referencję (adres) do wskazanego pliku biblioteki (tym samym kompilator VisualStudio będzie wiedział, gdzie szukać tego pliku). Po dołączeniu referencji do biblioteki CValidacja.dll w oknie referencji tworzonego rozwiązania znajdziemy stosowny wpis. 27 3.5.3 CDataGridPrint 28 3.6 Formularz frmNowaSala Zaprojektujemy teraz formularz przeznaczony do zarejestrowania nowej sali dydaktycznej. Zaczynamy od dodania do naszego rozwiązania nowego obiektu typu Windows Form, któremu nadajemy nazwę frmNowaSala. Na powierzchni formularza umieścimy następujące obiekty (formanty): TextBox o nazwie txtNumer – tu będziemy oczekiwać numeru sali; CheckBox o nazwie chbProjektor – pozwoli na określenie czy w sali jest projektor; ComboBox o nazwie cboTypSali – będziemy w nim wybierać typ sali; DataGridView o nazwie dgvZdjecia – w tym formancie będzie można wpisać (w taki lub inny sposób nazwy plików zdjęć danej sali, jeżeli takowe będą); Button o nazwie btnZapisz – przycisk uruchamiający procedurę zapisu danych; Button o nazwie btnBrowse – przycisk uruchamiający okno typu OpenFileDialog, w którym użytkownik będzie mógł wskazać pliki zdjęć danej sali; Label z domyślną nazwą – formanty opisujące pole tekstowe, pole kombi i datagrid. Dodatkowo do projektu formularza dodajemy formant typu ErrorProvider, który zostaje umieszczony na tzw. „tacy”. Nazwę tego formantu można pozostawić bez zmiany (domyślną). Poniżej widok projektu tego formularza, jego właściwość FormBorderStyle została zmieniona na FixedToolWindow, co nie pozwoli na zmianę jego rozmiaru. 29 W kodzie klasy tego formularza musimy utworzyć szereg procedur obsługujących jego funkcjonowanie, będą to oczywiście procedury zdarzeniowe. Kod zaczyna się od zaimportowania przestrzeni nazw CValidacjaJG, jest to przestrzeń nazw wykorzystywana przez bibliotekę DLL, którą wykorzystamy do walidacji wprowadzonych danych. Imports CValidacjaJG Public Class frmNowaSala ' pole tekstowe txtNumer powinno umożliwić wprowadzenie tylko ' znaków cyfr (0 do 9), klawisza Back oraz Delete ' Procedura obsługująca zdarzenie KeyPress realizuje to zadanie Private Sub txtNumer_KeyPress(ByVal sender As Object, ByVal e As _ System.Windows.Forms.KeyPressEventArgs) _ Handles txtNumer.KeyPress If e.KeyChar >= "0" AndAlso e.KeyChar <= "9" Then ' cyfry OK e.Handled = False ElseIf Asc(e.KeyChar) = Keys.Back Or _ Asc(e.KeyChar) = Keys.Delete Then ' delete i backspace ok e.Handled = False Else' reszta nie jest OK Beep() e.Handled = True End If End Sub Warto zauważyć, że procedura ta może nam się przydać także w innych przypadkach, a więc warto ją utworzyć w module wspólnym, wtedy będzie ją można wykorzystywać wielokrotnie. Poniżej kod tej procedury (w module wspólnym). Public Function ObslugaKeyPress(ByVal znak As Char) As Boolean If znak >= "0" AndAlso znak <= "9" Then ' cyfry OK Return False ElseIf Asc(znak) = Keys.Back Or Asc(znak) = Keys.Delete Then 'delete i backspace ok Return False Else' reszta nie OK Beep() Return True End If End Function Mając napisaną tę procedurę (funkcję) procedura obsługi zdarzenia KeyPress pola tekstowego txtNumer jest bardzo prosta. 30 Private Sub txtNumer_KeyPress(ByVal sender As Object, ByVal e As _ System.Windows.Forms.KeyPressEventArgs) _ Handles txtNumer.KeyPress e.Handled = ObslugaKeyPress(e.KeyChar) End Sub Procedura obsługująca zdarzenia Load formularza musi utworzyć źródło danych dla formantu dgvZdjecia. Private Sub frmNowaSala_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load ' w formularzu jest formant dgvZdjecia, nie jest podpięty ' pod żadne źródło danych. Jeżeli chcemy mieć możliwość dodawania ' nazw plików zdjęć sal, to musimy utworzyć obiekt DataTable Dim dt As New DataTable ' a następnie dodać do niego nową kolumnę typu tekstowego dt.Columns.Add(New DataColumn("Zdjecie", GetType(String))) ' można teraz przypisać obiekt dt jako źródło gridu Me.dgvZdjecia.DataSource = dt End Sub Procedura obsługująca zdarzenie Click przycisku btnBrowse tworzy i wyświetla okna dialogowe typu OpenFileDialog oczekując na wskazanie plików zdjęć danej sali. Private Sub btnBrowse_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnBrowse.Click Dim t() As String ' naszym zamiarem jest dopisanie wiersza lub wierszy do gridu ' deklarujemy obiekt dt jako DataTable i przypisujemy mu źródło ' danych gridu Dim dt As DataTable = Me.dgvZdjecia.DataSource ' deklarujemy i tworzymt egzemplarz klasy OpenFileDialog Dim ofd As New OpenFileDialog ' konstrukcja wiążąca z uwagi na konieczność zdefiniowania kilku ' właśności tego obiektu With ofd .Title = "Proszę wskazać plik zdjęcia (jpg, gif lub bmp)" .InitialDirectory = "E:\" .Multiselect = True .Filter = "Obrazy JPEG|*.jpg|Obrazy GIF|*.Gif|Obrazy Bmp|*.bmp|Wszystkie pliki|*.*" End With ' otwieramy okno i badamy, czy użytkownik wybrał jeden lub ' więcej plików 31 If ofd.ShowDialog = Windows.Forms.DialogResult.OK Then ' wybrał, przeglądamy kolekcję nazw plików (pełna ścieżka) For Each strFile As String In ofd.FileNames ' metoda Split dzieli ścieżkę na fragmenty opisujące ' dysk i foldery t = strFile.Split("\") ' ostatni element to potrzebna nazwa pliku ' dodajemy go do obiekty dt korzystając z metody Add dla ' Rows. Wstawiamy ostatni element tablicy t, jego indeks ' to UBound(t) dt.Rows.Add(t(UBound(t))) Next ' odświeżamy obiekt dgv Me.dgvZdjecia.Refresh() End If End Sub Musimy jeszcze napisać procedurę obsługującą zdarzenie Click przycisku btnZapisz, ale wcześniej musimy utworzyć klasę, która będzie odpowiadała za wykonanie tego polecenia i kliku innych jeszcze związanych z obsługą sal. 3.6.1 Klasa CAdministracja Będzie to klasa potomna dziedzicząca po klasie CForStorageSub. Utworzymy w niej interfejs o nazwie ISale, będzie on zawierał funkcje i metody związane z operacjami związanymi z salami dydaktycznymi (dodanie nowej sali, edycja, usunięcie). Zaczynamy od dodania do rozwiązania nowego obiektu typu Class, któremu nadajemu nazwę CAdministracja. W kodzie tej klasy wpisujemy najpierw instrukcję importu potrzebnych przestrzeni nazw. Imports System Imports System.Data Imports System.Data.SqlClient Po instrukcjach importu deklarujemy interfejs ISale podając nazwy procedur i ich parametry. Public Interface ISale Function Komunikat() As String Sub PrzygotujNowaSala(ByVal strConn As String, _ ByVal frm As frmNowaSala) Sub ZapiszSale(ByVal strConn As String, ByVal frm As frmNowaSala) End Interface 32 Właściwy kod klasy zaczynamy od instrukcji dziedziczenia po klasie bazowej, dalej będzie instrukcja o implementacji interfejsu ISale oraz deklaracje zmiennych prywatnych klasy. Public Class CAdministracja Inherits CForStorageSub Implements ISale Private mKomunikat As String = "" Kolejno tworzymy instrukcje opisujące sposób działania funkcji i procedur interfejsu ISale. Funkcja Komunikat zwraca zmienną prywatną klasy mKomunikat, zgodnie z naszą koncepcją będzie to komunikat precyzujący ewentualny błąd czasu wykonania. Public Function Komunikat() As String Implements ISale.Komunikat Return mKomunikat End Function Procedura (metoda) PrzygotujNowaSala odpowiada za pobranie z bazy danych dwóch pól z tabeli RodzajSal, które wykorzystamy jako źródło danych pola kombi cboTypSali. Pobranie będzie wykonane poprzez wywołanie procedury przechowywanej o nazwie pDajTypSali. Public Sub PrzygotujNowaSala(ByVal strConn As String, _ ByVal frm As frmNowaSala) _ Implements ISale.PrzygotujNowaSala Dim conn As New SqlConnection(strConn) Dim dt As DataTable Try conn.Open() ' pobranie z bazy rekordestu, który będzie źródłem danych ' dla pola kombo cboTypSali, procedura SQL nie ma parametrów dt = MyBase.DajRekordset(conn, "dbo.pDajTypSali", "brak") ' zmienna bFlaga zostaje ustawiona na False, dzięki temu ' w trakcie przypisywania właściwości DisplayMemeber oraz ' ValueMember ' będziemy mogli szybko opuścić procedurę obsługi zdarzenia ' SelectedIndexChange w frmNowaSala dla cboTypSali bFlaga = False ' stosujemy strukturę wiążącą With frm.cboTypSali .DataSource = dt .DisplayMember = "rodzaj" 33 .ValueMember = "idtyp" .SelectedIndex = -1 End With ' przypisujemy do bFlaga=True dla aktywacji procedury ' SelectedIndexChange Catch ex As Exception mKomunikat = "Problem z pobraniem danych dla typów sal" Finally ' sprzątamy po sobie conn.Close() conn = Nothing End Try End Sub Procedura (metoda) ZapiszSale odpowiada za zrealizowanie zapisu wprowadzonych informacji do tabeli Sale, a jeżeli użytkownik podał nazwę zdjęcia (lub nazwy zdjęć), to zostaną one zapisane do tabeli ZdjeciaSal. Public Sub ZapiszSale(ByVal strConn As String, _ ByVal frm As frmNowaSala) Implements ISale.ZapiszSale Dim conn As New SqlConnection(strConn), i, j As Integer Try conn.Open() ' zapisujemy dane do tabeli Sale, ale tylko wtedy, gdy ' numner sali jest unikalny i = MyBase.DajWartosc(conn, "dbo.pWstawSale", _ "@idnr", frm.txtNumer.Text, jgTyp.jgInteger, 0, _ "@projektor", IIf(frm.chbProjektor.Checked, 1, 0), _ jgTyp.jgInteger, 0, _ "@idtyp", frm.cboTypSali.SelectedValue, _ jgTyp.jgInteger, 0, _ "@TakNie", jgTyp.jgInteger, 0) If i = 0 Then ' sala zarejestrowana, można wstawić zdjęcia, jeżeli są If frm.dgvZdjecia.Rows.Count > 1 Then ' są, zapisujemy MyBase.WstawWieleRekordow(conn, "dbo.pWstawZdjecie", _ frm.dgvZdjecia, _ "@idnr", jgTyp.jgInteger, 0, True, frm.txtNumer.Text, _ "@foto", jgTyp.jgString, 30, False, 0) End If End If Catch ex As Exception 34 mKomunikat = _ "Problem z zapisaniem danych do tabel Sale i ZdjeciaSal" Finally conn.Close() conn = Nothing If i = 1 And mKomunikat.Length = 0 Then mKomunikat = "Sala o numerrze " & frm.txtNumer.Text & _ " juz jest w bazie, odmowa zapisu" End If End Try End Sub End Class 3.6.2 Uzupełnienie kodu klasy frmNowaSala Możemy już napisać procedurę obsługującą klik przycisku btnZapisz. Skorzystamy w tym kodzie z dwóch klas: CValidacja i CAdministracja. Private Sub btnZapisz_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnZapisz.Click ' polecenie zapisu ma być wykonane wtedy, gdy użytkownik poda ' poprawne informacje. Do ich zweryfikowania wykorzystamy metody ' udostępnione w bibliotece CValidacja ' deklarujemy i tworzymy egzemplarz tej klasy Dim w As New CValidacja ' badamy, czy pole txtNumer jest liczbą z podanego zakresu ' numeracji sal If Not w.CzyTextBoxToLiczba(True, Me.txtNumer, _ Me.ErrorProvider1, "Numer sali musi być podany!", minNrSali, _ maxNrSali, True) Then Exit Sub If Not w.CzyWybranoPozycjeCombo(True, Me.cboTypSali, _ Me.ErrorProvider1, "Proszę wybrać typ sali!", True) _ Then Exit Sub ' można zapisywać w = Nothing ' deklarujemy obiekt klasy CAdministracja wykorzystując interfejs ' ISale (co ograniczy dostępność metod do tego interfejsu) Dim z As ISale ' tworzymy egzemplarz klasy CAdministracja z = New CAdministracja ' wywołujemy metodę ZapiszSale z.ZapiszSale(strBaza, Me) ' sprawdzamy, czy zapis zakończył się sukcesem? If z.Komunikat.Length > 0 Then 35 MsgBox(z.Komunikat, MsgBoxStyle.Critical, Me.Text) Else MsgBox("Nowa sala zapisana w bazie", MsgBoxStyle.Information, _ Me.Text) Me.Close() End If z = Nothing End Sub 3.6.3 Uzupełnienie kodu formularza głównego Musimy jeszcze zmodyfikować instrukcje uruchamiające formularz frmNowaSala, chodzi o to, że ten formularz ma prawo być pokazany tylko wtedy, gdy wszystkie jego formanty będą gotowe do pracy. W przypadku tego formularza musimy przed jego pokazaniem dostarczyć dane źródłowe do formantu kombo o nazwie cboTypSal. Zrobimy to poprzez utworzenie obiektu klasy CAdministracja i wywołanie metody PrzygotujNowaSala. Pokazana niżej procedura korzysta z publicznej procedury PokazForm, która została utworzona w module ogólnym (z uwagi na to, że będziemy wielokrotnie z niej korzystać). Private Sub mnuNowaSala_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles mnuNowaSala.Click Dim frm As New frmNowaSala Dim w As ISale w = New CAdministracja w.PrzygotujNowaSala(strBaza, frm) PokazForm(w.Komunikat, frm, Me, _ "Błąd otwarcia formularza frmNowaSala", _ "Formularz dodania nowej sali") End Sub Wspomniana procedura PokazForm otrzymuje jako argumenty te informacje, które pozwalają jej na wyświetlenie lub nie danego formularza. Public Sub PokazForm(ByVal strKomunikat As String, _ ByRef frm As Form, ByRef frmParent As Form, _ ByVal strOpisOknaKomunikatu As String, _ Optional ByVal strTytulFormy As String = Nothing) If strKomunikat.Length > 0 Then MsgBox(strKomunikat, MsgBoxStyle.Critical, _ strOpisOknaKomunikatu) Else If Not IsNothing(strTytulFormy) Then 36 frm.Text = strTytulFormy End If frm.MdiParent = frmParent frm.Show() End If End Sub 3.6.4 Formularz frmNowaSala w „pracy” Na zakończenie kilka zrzutów ekranowych pokazujących działanie tego formularza. 37 I zapis w bazie, tabela Sale (ostatnia pozycja w tej tabeli) oraz w ZdjeciaSal (dwa ostatnie rekordy). 38