• No results found

Ramverksimplementation

In document Elektroniskt Skyttesystem (Page 45-59)

4 Implementation

4.3 Visningsklient

4.3.1 Ramverksimplementation

Två av visningsklientens klasser, träffklassen Shot och kontextklassen Context, skapas utifrån databasen då ramverket Entity Framework implementeras. För att implementera ramverket i projektet så behövs en ny programdel ADO.NET Data Entity Model (se avsnitt 3.3.2) läggas till. Modellen sätts upp som en Code First-lösning, vilket ser till att klasser i modellen skapas utifrån den redan existerande databasen. En utvecklare får då möjlighet att välja databasuppkoppling, modellinställningar och vilka databasobjekt som inkluderas i modellen. I samband med att uppkopplingen till databasen sätts upp behöver utvecklaren förse de inloggningsuppgifter som angetts när servern skapades i Azureportalen. Dessa inloggningsuppgifter krävs för att koppla visningsklienten till databasen, och utvecklaren kan sedan välja att skicka med dessa i den uppkopplingsträng som skapas i slutet av ramverksimplementationen, eller om de ska förses på annat sätt i själva applikationen. I detta projekt gjordes antagandet att skicka med uppgifterna i uppkopplingssträngen, då fokus under utvecklingsprocessen låg på att utveckla en fungerande visningsklient, inte på dess säkerhet. Uppkopplingsinställningarna sparas som en given sträng, i detta fall den antagna strängen

Context, i applikationens konfigureringsfil App.config. Denna fil innehåller ramverkets

uppkopplingskonfiguration mellan applikationen och databasen. Denna konfiguration innefattar den skapade uppkopplingsträngen. Uppkopplingsträngens relevanta innehåll är följande:

 Name: den givna strängen som uppkopplingsinställningarna sparades under  Data Source: den server som agerar datakälla

 Initial Catalog: den databas som uppkopplingen upprättats mot

 User id & password: de inloggningsuppgifter som är kopplade till servern, och som i detta fall valts att medskickas i uppkopplingssträngen

Notera att känslig information i koden som följer har bytts ut mot stjärnsymboler; detta på grund av att SQL-servern och dess inloggningsuppgifter tillhandahållits av Sogeti.

<connectionStrings>

<add name="Context" connectionString="data source=**********.database.windows.net; initial catalog=ElectronicMarking; user id=electronicMarking;

password=******************" providerName="System.Data.SqlClient" /> </connectionStrings>

Ramverksimplementationen resulterar i två genererade klasser: en kontextklass som hanterar uppkopplingen mellan applikationen och databasen, och en träffklass som representerar den databastabell som utvecklaren har möjlighet att inkludera i den skapade modellen.

Kontextklassen innehåller en konstruktor, där den givna strängen Context används för att skapa en ny kontextinstans för uppkopplingen till databasen, samt get- och set-metoder till de entiteter som kan förfrågas i databasen; i detta fall är dessa entiteter träffarna shots, som är en databassamling DbSet av typen Shot.

public partial class Context : DbContext

{

public Context()

: base("name=Context") {

}

public virtual DbSet<Shot> shots { get; set; } }

Träffklassen innehåller get- och set-metoder till de egenskaper som databasentiteterna innehar (se avsnitt 4.2), samt information rörande databasens primärnycklar. Informationen rör de kolumner som primärnycklarna mappas till, samt eventuella sätt som egenskapsvärden genereras. Primärnycklarna Id och ShotNum är båda av typ int, och träffarnas x- samt y-koordinatsvärden, xPos respektive yPos, är av typen double.

public partial class Shot

{

[Key]

[Column(Order = 0)]

[DatabaseGenerated(DatabaseGeneratedOption.None)] public int Id { get; set; }

[Key]

[Column(Order = 1)]

[DatabaseGenerated(DatabaseGeneratedOption.None)] public int ShotNum { get; set; }

public double yPos { get; set; } }

4.3.2 Användargränssnitt

Visningsklientens användargränssnitt utvecklas i utvecklingsmiljöns vydesigner. Tack vare att projektet utvecklas i WPF så finns det ytterligare utvecklingsmöjligheter genom dess XAML-designer (se avsnitt 3.7.1). Visningsklienten ges däri namnet Visningsklient, och sätts till en antagen fönsterstorlek. Resten av dess funktionalitet sätts i en kanvaskontroll, som innehåller den samling objekt som användargränssnittet kommer bestå utav.

<Window x:Class="Elektroniskt_Skyttesystem_Visningsklient.MainWindow"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Elektroniskt_Skyttesystem_Visningsklient" mc:Ignorable="d"

Title="Visningsklient" Height="350" Width="480">

<Canvas Name="TargetCanvas">

Det som antogs behöver visas upp för en användare är en träffyta, där dennes träffar möjligtvis kan visualiseras utifrån var användaren själv träffat på den fysiska träffytan. Träffytan i användargränssnittet är ursprungligen en Scalable Vector Graphics-fil (svg) hämtad på inrådan av Sogeti från Internet [35], för att möjliggöra en mer detaljerad träffpresentation i visningsklientens användargränssnitt. Filformatet svg stöds inte av XAML-designern, så den behöver först konverteras till ett tillåtet sådant; detta går att genomföra genom att utnyttja applikationen Inkscape [36] för att konvertera filen till en Portable

Network Graphics-bild (png). Träffytan kan därefter tilldelas en antagen storlek inuti

användargränssnittet.

<Image Name="Target" Source="C:\Users\abergman\Documents\Visual Studio

2015\Projects\Elektroniskt Skyttesystem Visningsklient\10_m_Air_Rifle_target.png"

Height="321" Width="345" />

Ytterligare användargränssnittkomponenter väljs från designerns verktygslåda. Användarens beräknade träffresultat väljs att presenteras i en List Box. Dock så får en användare då

Detta utförs genom att i XAML-designern sätta en ofokuserbar egenskap för alla artiklar som rutan innehåller; när således en användare försöker att markera ett värde i listan så händer ingenting. En List Box har en egenskap ItemContainerStyle, som kan sätta stilen för renderade artiklar däri; det är således dessa artiklar ListBoxItems man väljer som stilmålgrupp. Således väljs artikelegenskapen Focusable, som ges ett värde False för att göra artiklarna ofokuserbara.

<ListBox x:Name="listBox" Height="150" Width="100" Canvas.Left="350" Canvas.Top="60">

<ListBox.ItemContainerStyle>

<Style TargetType="ListBoxItem">

<Setter Property="Focusable" Value="False"/>

</Style>

</ListBox.ItemContainerStyle> </ListBox>

Det beräknade medelresultatet väljs att presenteras i en Text Box. Dess innehåll ska ej kunna påverkas av en användare, utan ska endast visa det avsedda, beräknade medelresultatet. Detta utförs genom att ange värdet True i knappens egenskap IsReadOnly. Rutans antagna storlek och position kan sedan anges.

<TextBox x:Name="textBox" IsReadOnly="True" Height="23" Width="100" Canvas.Left="350"

Canvas.Top="236"/>

Två etiketter för Träffresultat och Medelresultat befinner sig över respektive ruta i syfte att förtydliga rutinnehåll för en användare. Efter att deras innehåll har sätts så tilldelas de antagna positioner.

<Label x:Name="label" Content="Träffresultat:" Canvas.Left="363" Canvas.Top="30"/> <Label x:Name="label2" Content="Medelresultat:" Canvas.Left="363" Canvas.Top="210"/>

Till sist placeras den knapp som en användare använder för att uppdatera användargränssnittet med alla träffar och beräknade resultat. För att förtydliga för en användare vad knappens funktion är så sätts dess innehåll till Uppdatera, och dess händelsehanterare vid knapptryck kopplas till en metod button_Click. Knappen tilldelas sedan antagna storlekar och positioner i användargränssnittet. I och med att knappen är fönstrets sista komponent stängs därefter först kanvaskontrollen och sedan fönstret i XAML-koden.

<Button x:Name="button" Content="Uppdatera" HorizontalAlignment="Left"

VerticalAlignment="Top" Width="75" Grid.Column="1" Grid.Row="1" Click="button_Click"

Canvas.Left="375" Canvas.Top="264"/>

</Canvas> </Window>

Figur 4.3 illustrerar det slutgiltiga användargränssnittet i utvecklingsmiljöns WPF-designer. Dess träffyta befinner sig på fönstrets vänstra sida, och till höger återfinns knappen samt de rutor och etiketter som tidigare beskrivits.

Figur 4.3: Visningsklientens användargränssnitt i utvecklingsmiljöns WPF-designer

4.3.3 Vyklass

Visningsklientens vyklass har i uppgift att visualisera träffar och beräknade resultat i användargränssnittet, samt hantera händelsen då en användare trycker på knappen däri. Följande avsnitt beskriver dess klassegenskaper, konstruktor och metoder.

4.3.3.1 Klassegenskaper

Först instansieras ett objekt DH, som är en instans av datahanteringsklassen DataHandler. Man får därigenom möjlighet att anropa metoder i denna klass från vyklassen. Därefter definieras en referens averageEllipse, av typen Ellipse. Den behöver vara en egenskap av skäl som tas upp i avsnitt 4.3.3.6.

4.3.3.2 MainWindow-konstruktorn

I huvudfönstrets konstruktor finns ett anrop till metoderna InitializeComponent, som laddar användargränssnittet, och clearDB i datahanteringsklassen. Detta andra metodanrop görs i syfte att starta applikationen med en tom databas, redo att ta emot nya användarträffar.

public MainWindow() { InitializeComponent(); DH.clearDB(); } 4.3.3.3 visualizeScoreInListBox

Denna metod tar emot en parameter result av typen double, som representerar ett träffresultat. Detta resultat läggs sedan in som en artikel i användargränssnittets List Box, på dess första position. Detta antogs vara ett användarvänligt val, då en användares senaste träff då alltid befinner sig högst upp i listan, följt av dess föregångare.

private void visualizeScoreInListBox(double result) {

listBox.Items.Insert(0, result); }

4.3.3.4 visualizeAverageScoreInTextBox

Även denna metod tar emot en parameter av typen double, här kallad averageResult. Den representerar det beräknade medelresultatet för alla träffresultat. Textrutans innehåll sätts till detta resultat, efter att det konverteras från ett numeriskt värde till en textsträng.

private void visualizeAverageScoreInTextBox(double averageResult) {

textBox.Text = averageResult.ToString(); }

4.3.3.5 visualizeShot

Denna metod tar emot en träffparameter shot av typen Shot. Därefter skapas ett objekt

shotEllipse, som är en ny instans av ellipsklassen. Denna träffellips tilldelas sedan ett par

private void visualizeShot(Shot shot) {

Ellipse shotEllipse = new Ellipse(); shotEllipse.Width = 5;

shotEllipse.Height = 5;

Då träffellipsens koordinater i x- och y-led på träffytan ska sättas behöver man ta hänsyn till hur ellipser placeras i vyn. När man skapar och placerar ellipsformer i en vy så görs detta från kvadrater innehållande dessa ellipsformer, som illustreras i Figur 4.4. Det är från denna kvadrats övre vänstra hörn som ellipsformen sedan styrs av de värden man tilldelar dess x- och y-koordinater. De x- och y-koordinater som träffarna har i databasen och därefter representerar deras approximerade mittpunkt. För att den skapade träffellipsen ska få de önskade x- och y-koordinaterna (x’ och y’ i Figur 4.4) så måste de koordinater man placerar ellipsen med reduceras med hälften av dess angivna bredd respektive höjd för att nå dess mittpunkt. Dessa reducerade x- och y-koordinater tilldelas träffellipsen genom att anropa dess

SetValue-metod i vänsterled och toppled.

shotEllipse.SetValue(Canvas.LeftProperty, shot.xPos - (shotEllipse.Width / 2)); shotEllipse.SetValue(Canvas.TopProperty, shot.yPos - (shotEllipse.Height / 2));

För att sedan färglägga träffellipsen behövs ett objekt solidColorBrush skapas, som är en instans av klassen SolidColorBrush. Dess färgegenskap Color tilldelas ett värde med hjälp av metoden FromArgb. Denna metod skapar en färgstruktur utifrån alpha (transparens), samt färgerna röd, grön och blå. Transparensargumentet sätts till maxvärdet tvåhundrafemtiofem (255), vilket även tilldelas det röda värdet; resterande färgargument sätts till noll, och träffellipsen fylls därefter. Det som sedan återstår är att lägga till träffellipsen i kanvaskontrollen som ett barnelement till denna.

SolidColorBrush solidColorBrush = new SolidColorBrush(); solidColorBrush.Color = Color.FromArgb(255, 255, 0, 0); shotEllipse.Fill = solidColorBrush;

TargetCanvas.Children.Add(shotEllipse); }

Figur 4.4: Kvadratinramad ellipsform, med faktiska och önskade koordinater

4.3.3.6 visualizeAverageShot

Denna metod har i uppgift att visualisera den medelträff som en användares träffserie resulterat i. Notera att denna medelträff utgår ifrån alla träffars koordinater, inte resultat, vilket frikopplar denna träffellips från det presenterade medelresultatet i användargränssnittets Text Box.

Metodens kodstruktur liknar visualiseringsmetoden för övriga träffar, med några modifikationer. Först så undersöker en if-sats om kanvaskontrollen redan innehåller en medelträff averageEllipse, och om så är fallet så tas denna bort. Detta utförs så att inte fler än en medelträff återfinns i träffytan i användargränssnittet, och som är skälet till att referensen till medelträffen behövde vara en klassegenskap. Därefter sätts referensen till medelträffellipsen till en ny instans av ellipsklassen, och tilldelas antagna bredd- och höjdvärden.

private void visualizeAverageShot() {

if (TargetCanvas.Children.Contains(averageEllipse)) TargetCanvas.Children.Remove(averageEllipse); averageEllipse = new Ellipse();

averageEllipse.Width = 5; averageEllipse.Height = 5;

Där medelträffellipsens x- och y-koordinater ska sättas behövs ett par anrop till metoderna

calculateAverageXPosition och calculateAverageYPosition i datahanteringsklassen. Dessa

metoder har i uppgift att räkna ut medelvärdet för alla träffars x- respektive y-koordinater, och sedan returnera dessa. Dessa returnerade koordinater används således i de tidigare nämnda

SetValue-metoderna i vänsterled och toppled i kanvaskontrollen, subtraherade med ellipsen

averageEllipse.SetValue(Canvas.LeftProperty, DH.calculateAverageXPosition() - (averageEllipse.Width / 2));

averageEllipse.SetValue(Canvas.TopProperty, DH.calculateAverageYPosition() - (averageEllipse.Height / 2));

Ett antagande gjordes att färgen som tilldelas medelträffellipsen bör skilja sig från den som tilldelas övriga visualiserade träffar för att en användare lättare ska kunna överblicka resultatet i användargränssnittet. Genom att ge även argumentvärdet för grön färg maxvärdet tvåhundrafemtiofem (255), så fylls sedan medelträffellipsen med en gul färg. Medelträffellipsen läggs på samma sätt som tidigare med i kanvaskontrollen som ett barnelement till denna.

SolidColorBrush solidColorBrush = new SolidColorBrush(); solidColorBrush.Color = Color.FromArgb(255, 255, 255, 0); averageEllipse.Fill = solidColorBrush;

TargetCanvas.Children.Add(averageEllipse); }

4.3.3.7 button_Click

Denna metod är en händelsehanteringsmetod som anropas om en användare trycker på knappen i användargränssnittet. Den har i uppgift att sköta uppdateringen av användargränssnittet, genom att anropa alla relevanta metoder i visningsklientens olika klasser.

Först anropas en metod loadDBShotsIntoList i datahanteringsklassen, som har i uppgift att läsa in alla träffar i databasen och lägga in det i visningsklientens lokala lista. Därefter töms innehållet i användargränssnittets List Box, så att inte tidigare träffserier återfinns däri när ny data således ska presenteras. En if-sats undersöker sedan huruvida antalet inlästa träffar i listan är noll utifrån en egenskapsvariabel i datahanteringsklassen som avser rymma träffantalet i listan. Om så är fallet så returneras man ut ur metoden. Syftet med detta är att förhindra att en användare försöker uppdatera utifrån en tom lista; då beräkningen av medelresultat utförs så sker det då med en nolldivision och resultatet presenteras som symboler i användargränssnittet. Med denna lösning så går processen inte så långt, utan man

private void button_Click(object sender, RoutedEventArgs e) {

DH.loadDBShotsIntoList(); listBox.Items.Clear();

if (DH.shotsInList == 0) return;

Om det istället existerar inlästa träffar i listan så ska metoden för varje sådan träff i listan visualisera dess beräknade träffresultat och själva träffen i användargränssnittet. En lokal variabel result av typen double tilldelas ett returnerat värde från metoden calculateScore i datahanteringsklassen. Denna metod har i uppgift att räkna ut ett träffresultat utifrån träffens koordinater utifrån avståndet från träffytans mittpunkt. Denna mittpunkt beräknas utifrån träffytebildens antagna bredd- och höjdvärden, så för att undkomma hårdkodade sådana värden i beräkningsmetoden måste dessa värden skickas med som argument i metodanropet; detta eftersom datahanteringsklassen, till skillnad från vyklassen, är skild från vyn och dess kontroller, och kan således inte komma åt dessa värden på egen hand. Även träffen som läses ut för varje varv i listan skickas med som ett argument i metodanropet. Träffresultatet och själva träffen visualiseras sedan genom att resultatet och träffen skickas som argument i metodanropen av metoderna visualizeScoreInListBox respektive visualizeShot.

foreach (Shot shot in DH.listShots) {

double result = DH.calculateScore(shot, Target.Width, Target.Height); visualizeScoreInListBox(result);

visualizeShot(shot); }

När alla träffar i listan således hanterats behöver metoden även visualisera det beräknade medelresultatet för alla träffresultat och själva medelträffen i användargränssnittet. Funktionaliteten i denna del liknar den för varje individuell träff: En lokal variabel

averageResult av typen double tilldelas ett returnerat värde efter ett metodanrop till metoden calculateAverageScore i datahanteringsklassen, som skickas som med som ett argument till

metoden visualizeAverageScoreInTextBox, innan metoden visualizeAverageShot anropas.

double averageResult = DH.calculateAverageScore(); visualizeAverageScoreInTextBox(averageResult); visualizeAverageShot();

4.3.4 Datahanteringsklass

Visningsklientens datahanteringsklass avser hantera databasrensning, inläsning av träffar i en lista, samt beräkning av träffresultat och alla medelresultat. Följande avsnitt beskriver datahanteringsklassens klassegenskaper och metoder.

4.3.4.1 Klassegenskaper

En egenskapsreferens listShots, som är en lista av träffar, och två klassegenskapsvariabler

shotsInList och totalResult definieras, av typerna int respektive double. listShots ämnas

innehålla alla inläsa träffar, shotsInList det antal träffar som finns inlästa i listan och totalResult det totala resultatet som alla träffar genererat. listShots och shotsInList är publika, så att deras innehåll går att komma åt i vyklassen. Variabeln totalResult är privat, då dess innehåll endast behövs i datahanteringsklassen.

public List<Shot> listShots;

public int shotsInList;

private double totalResult;

4.3.4.2 clearDB

Denna metod har för avsikt att tömma databasen på gamla värden, vilket gör den redo för en ny träffserie. Metoden är publik, då den anropas i vyklassen i samband med att användargränssnittet laddas. Metoden sätter upp en uppkoppling till databasen genom att använda en using-sats med kontexten context. Räckvidden för denna sats är hela metoden, och när metoden är till ända så görs den av med. För varje träffentitet shot som befinner sig i databasen utförs en borttagning med hjälp av en Remove-metod. När således alla träffar hanterats så sparas denna ändring i kontexten.

public void clearDB() {

using (Context context = new Context()) {

foreach (Shot shot in context.shots) {

4.3.4.3 loadShotsIntoList

Denna metod har i uppgift att för varje träff i databasen läsa in denna i en lista. Till att börja med instansieras egenskapsreferensen listShots till en ny träfflista, och klassegenskapsvariablerna shotsInList och totalResult nollställs.

public void loadDBShotsIntoList() {

listShots = new List<Shot>(); shotsInList = 0;

totalResult = 0;

Som I föregående metod sätts en uppkoppling mot databasen upp med hjälp av en using-sats med kontexten context. För varje träff däri så läggs dess enhetsidentitet, träffnummer, samt x- och y-koordinater in i en ny träff i listan. Räknaren shotsInList inkrementeras därefter för varje tillagd listträff.

using (Context context = new Context()) {

foreach (Shot shot in context.shots) {

listShots.Add(new Shot { Id = shot.Id, ShotNum = shot.ShotNum, xPos = shot.xPos, yPos = shot.yPos });

shotsInList++; }

} }

4.3.4.4 calculateScore

Denna metod har i uppgift att beräkna ett träffresultat utifrån träffens avstånd från träffytans mittpunkt.

Metoden tar emot tre parametrar: en träff shot av typen Shot, samt träffytans breddvärde

targetWidth och dess höjdvärde targetHeight, båda av typen double. Därefter definieras och

instansieras flera lokala variabler av typen double. Variablerna midX och midY ämnas innehålla träffytans mittpunkt i x- respektive y-led, så de tilldelas parametervärdena för höjd och bredd dividerat med två. Variablerna deltaX och deltaY kommer att representera de avstånd som mäts upp mellan träffen och träffytans mittpunkt. Variablerna xPosition och yPosition tilldelas de x- respektive y-koordinater som den medskickade parameterträffen har. Tre variabler targetMaxResult, targetMinResult och targetFactor av typ int definieras till värde tio, ett respektive sexton. Dessa antagna värden fås utifrån den träffyta som används i

visningsklientens gränssnitt (se avsnitt 4.3.2), och används senare i metoden där träffresultatet beräknas.

public double calculateScore(Shot shot, double targetWidth, double targetHeight) {

double midX = (targetWidth / 2), midY = (targetHeight / 2), deltaX, deltaY, xPosition = shot.xPos, yPosition = shot.yPos;

int targetMaxResult = 10, targetMinResult = 1, targetFactor = 16;

Variablerna deltaX och deltaY ska innehålla de positiva avstånden mellan deras respektive koordinater och träffytans mittpunkt. Därigenom följer två if-satser, som undersöker huruvida träffens koordinat i de olika leden är mindre eller lika med denna mittpunkt. Om så är fallet tilldelas deltavariablerna värdet för mittpunktsvärdet subtraherat med koordinatvärdet. Om träffens koordinat istället är större än mittpunktsvärdet så tilldelas deltavariablerna värdet för koordinatvärdet subtraherat med mittpunktsvärdet.

if (xPosition <= midX) deltaX = midX - xPosition; else deltaX = xPosition - midX;

if (yPosition <= midY) deltaY = midY - yPosition; else deltaY = yPosition - midY;

Det antagna maxresultatet som en träff i träffytan kan uppnå har tidigare definierats i variabeln targetMaxResult. Detta maxresultat uppnår en träff antingen i mitten av träffytan, vilket innebär att träffens koordinater är desamma som träffytans mittkoordinater i x- och y-led, eller så nära mitten att avståndet i beräkningen avrundas till noll. Träffresultatet blir mindre ju längre från mitten en träff hamnar. En variabel result av typen double tilldelas resultatvärdet för en träff. Detta resultat beräknas utifrån maxresultatet, subtraherat med divisionen av det avstånd en träff har till mittpunkten och en viss träffytefaktor. Denna faktor är en i pixlar uppmätt medelbredd för alla poängområden i den använda träffytan, illustrerad i Figur 4.5. Avståndet beräknas med hjälp av Pythagoras sats, innehållande de deltavärden som beräknats för träffens x- och y-koordinater.

Notera att denna formel således är en antagen lösning utifrån den träffyta som används i gränssnittet, och som i detta fall resulterar acceptabla värden.

In document Elektroniskt Skyttesystem (Page 45-59)

Related documents