MainPage.xaml.cs
using Microsoft.Maui.Controls.Shapes;
using TestAplikacjeMobilne.Logika;
using TestAplikacjeMobilne.Modele;
namespace TestAplikacjeMobilne;
public partial class MainPage : ContentPage
{
private const string NazwaPlikuJson = "pytania_inf04_maui.json";
private const int IloscLosowanychPytan = 15;
private List<ModelePytanieTestowe> pytania = new();
private readonly Dictionary<int, int?> zaznaczenia = new(); // klucz: index pytania, wartosc: index odpowiedzi
public MainPage()
{
InitializeComponent();
}
protected override async void OnAppearing()
{
base.OnAppearing();
// Żeby nie dublować po przełączaniu w menu
if (pytania.Count > 0) return;
var baza = await LogikaTestu.WczytajBazePytanAsync(NazwaPlikuJson);
LabelOpis.Text = baza.NazwaTestu;
pytania = LogikaTestu.LosujPytania(baza.Pytania, IloscLosowanychPytan);
WyswietlPytania();
}
private void WyswietlPytania()
{
KontenerPytan.Children.Clear();
zaznaczenia.Clear();
for (int i = 0; i < pytania.Count; i++)
{
zaznaczenia[i] = null;
var p = pytania[i];
// Ramka (Border)
var border = new Border
{
Stroke = Colors.Gray,
StrokeThickness = 1,
Padding = 12,
StrokeShape = new RoundRectangle { CornerRadius = 10 }
};
var stack = new VerticalStackLayout { Spacing = 8 };
stack.Children.Add(new Label
{
Text = $"Pytanie {i + 1}",
FontAttributes = FontAttributes.Bold
});
stack.Children.Add(new Label
{
Text = p.Tresc,
FontSize = 15
});
string groupName = $"P{i}";
for (int j = 0; j < p.Odpowiedzi.Count; j++)
{
int indexPytania = i;
int indexOdp = j;
var rb = new RadioButton
{
Content = p.Odpowiedzi[j],
GroupName = groupName
};
rb.CheckedChanged += (s, e) =>
{
if (e.Value == true)
zaznaczenia[indexPytania] = indexOdp;
};
stack.Children.Add(rb);
}
border.Content = stack;
KontenerPytan.Children.Add(border);
}
}
private async void Sprawdz_Clicked(object sender, EventArgs e)
{
int punkty = 0;
for (int i = 0; i < pytania.Count; i++)
{
var zazn = zaznaczenia[i];
if (zazn.HasValue && zazn.Value == pytania[i].Poprawna)
punkty++;
}
await Shell.Current.GoToAsync(nameof(PodsumowaniePage),
new Dictionary<string, object>
{
{ "Punkty", punkty },
{ "Maks", pytania.Count }
});
}
}
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestAplikacjeMobilne.MainPage"
Title="TEST – MAUI">
<ScrollView>
<VerticalStackLayout Padding="20" Spacing="14">
<Label Text="TEST (losowanie 15 pytań)"
FontSize="24"
FontAttributes="Bold"
HorizontalOptions="Center"/>
<Label x:Name="LabelOpis"
Text=""
FontSize="14"
HorizontalOptions="Center"/>
<!-- tutaj wypełnimy pytania w kodzie -->
<VerticalStackLayout x:Name="KontenerPytan" Spacing="12" />
<Button Text="SPRAWDŹ"
FontSize="18"
Clicked="Sprawdz_Clicked" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
Logika/LogikaTestu.cs
using System.Text.Json;
using TestAplikacjeMobilne.Modele;
namespace TestAplikacjeMobilne.Logika;
public static class LogikaTestu
{
public static async Task<ModeleBazaPytan> WczytajBazePytanAsync(string nazwaPliku)
{
using var stream = await FileSystem.OpenAppPackageFileAsync(nazwaPliku);
using var reader = new StreamReader(stream);
string json = await reader.ReadToEndAsync();
var baza = JsonSerializer.Deserialize<ModeleBazaPytan>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
return baza ?? new ModeleBazaPytan();
}
public static List<ModelePytanieTestowe> LosujPytania(List<ModelePytanieTestowe> wszystkie, int ile)
{
// proste i czytelne losowanie
return wszystkie
.OrderBy(x => Guid.NewGuid())
.Take(ile)
.ToList();
}
}
Modele/ModelePytanieTestowe.cs
namespace TestAplikacjeMobilne.Modele;
public class ModelePytanieTestowe
{
public int Id { get; set; }
public string Tresc { get; set; } = "";
public List<string> Odpowiedzi { get; set; } = new();
public int Poprawna { get; set; } // indeks 0..3
}
public class ModeleBazaPytan
{
public string NazwaTestu { get; set; } = "";
public List<ModelePytanieTestowe> Pytania { get; set; } = new();
}
Resources/Raw/pytania_inf04_maui.json
{
"nazwaTestu": "INF.04 – .NET MAUI (losowanie 15/40)",
"pytania": [
{
"id": 1,
"tresc": "Który plik najczęściej zawiera układ interfejsu w .NET MAUI?",
"odpowiedzi": [ "Program.cs", "MainPage.xaml", "AppShell.xaml.cs", "launchSettings.json" ],
"poprawna": 1
},
{
"id": 2,
"tresc": "Jak nazywa się zdarzenie przycisku Button wywoływane po kliknięciu?",
"odpowiedzi": [ "Changed", "Clicked", "PressedKey", "Load" ],
"poprawna": 1
},
{
"id": 3,
"tresc": "Do czego służy Entry w MAUI?",
"odpowiedzi": [ "Do wprowadzania tekstu", "Do wyświetlania obrazu", "Do listy elementów", "Do nawigacji" ],
"poprawna": 0
},
{
"id": 4,
"tresc": "Który układ (layout) układa elementy jeden pod drugim?",
"odpowiedzi": [ "VerticalStackLayout", "Grid", "AbsoluteLayout", "FlexLayout (zawsze)" ],
"poprawna": 0
},
{
"id": 5,
"tresc": "Jak w MAUI odczytać plik dołączony jako asset (Resources/Raw)?",
"odpowiedzi": [ "File.ReadAllText(\"...\")", "FileSystem.OpenAppPackageFileAsync(\"...\")", "HttpClient.GetStringAsync(\"...\")", "Directory.GetFiles(\"...\")" ],
"poprawna": 1
},
{
"id": 6,
"tresc": "Który element w AppShell odpowiada za pozycję w menu bocznym (flyout)?",
"odpowiedzi": [ "FlyoutItem", "Label", "Entry", "BoxView" ],
"poprawna": 0
},
{
"id": 7,
"tresc": "Jak nazywa się nowoczesna nawigacja w Shell?",
"odpowiedzi": [ "Navigation.PushAsync()", "Shell.Current.GoToAsync()", "Window.Navigate()", "Page.Open()" ],
"poprawna": 1
},
{
"id": 8,
"tresc": "Co zwraca metoda int.TryParse(...) gdy konwersja się nie uda?",
"odpowiedzi": [ "Rzuca wyjątek", "Zwraca false", "Zwraca -1", "Zwraca null" ],
"poprawna": 1
},
{
"id": 9,
"tresc": "Który element najlepiej nadaje się do wyboru jednej opcji z kilku (jak w teście)?",
"odpowiedzi": [ "RadioButton", "Label", "Image", "ProgressBar" ],
"poprawna": 0
},
{
"id": 10,
"tresc": "Do czego służy IsPassword w Entry?",
"odpowiedzi": [ "Zamienia Entry w label", "Ukrywa wpisywane znaki", "Dodaje walidację e-mail", "Ustawia limit znaków" ],
"poprawna": 1
},
{
"id": 11,
"tresc": "Która kolekcja jest często używana do list w UI, bo powiadamia o zmianach?",
"odpowiedzi": [ "List<T>", "ObservableCollection<T>", "Stack<T>", "Dictionary<T,T>" ],
"poprawna": 1
},
{
"id": 12,
"tresc": "Który element w MAUI jest odpowiednikiem „pola tekstowego” wprowadzania danych?",
"odpowiedzi": [ "Entry", "Label", "Image", "Frame" ],
"poprawna": 0
},
{
"id": 13,
"tresc": "Jaką właściwością w Label ustawisz pogrubienie tekstu?",
"odpowiedzi": [ "FontAttributes", "TextDecorations", "BoldText", "Weight" ],
"poprawna": 0
},
{
"id": 14,
"tresc": "Do czego służy Grid w MAUI?",
"odpowiedzi": [ "Do układania wierszy i kolumn", "Tylko do obrazków", "Tylko do przycisków", "Do bazy danych" ],
"poprawna": 0
},
{
"id": 15,
"tresc": "Który zapis tworzy metodę obsługi zdarzenia Clicked?",
"odpowiedzi": [ "void Clicked()", "private void Button_Clicked(object s, EventArgs e)", "int Clicked(string x)", "async Task OnLoad()" ],
"poprawna": 1
},
{
"id": 16,
"tresc": "Po co stosuje się async/await w aplikacji mobilnej?",
"odpowiedzi": [ "Żeby UI nie „zamrażało” się", "Żeby XAML się kompilował", "Żeby przyciski działały", "Żeby zmienić kolor tła" ],
"poprawna": 0
},
{
"id": 17,
"tresc": "Która kontrolka pokazuje krótki tekst na ekranie?",
"odpowiedzi": [ "Label", "Entry", "Slider", "Switch" ],
"poprawna": 0
},
{
"id": 18,
"tresc": "Jak w MAUI wyświetlić szybkie okno z komunikatem (dialog)?",
"odpowiedzi": [ "MessageBox.Show(...)", "DisplayAlert(...)", "Console.WriteLine(...)", "Toast.Show(...) (zawsze)" ],
"poprawna": 1
},
{
"id": 19,
"tresc": "Co jest prawdą o RadioButtonach w jednej grupie?",
"odpowiedzi": [ "Można zaznaczyć wszystkie", "Można zaznaczyć tylko jeden", "Nie da się odznaczyć", "Nie mają GroupName" ],
"poprawna": 1
},
{
"id": 20,
"tresc": "Który plik zwykle zawiera ustawienie: MainPage = new AppShell(); ?",
"odpowiedzi": [ "App.xaml.cs", "Program.cs", "MainPage.xaml", "MauiProgram.cs" ],
"poprawna": 0
},
{
"id": 21,
"tresc": "Jak nazywa się plik, w którym rejestrujesz usługi DI w MAUI?",
"odpowiedzi": [ "MauiProgram.cs", "AppShell.xaml", "MainPage.xaml.cs", "Resources.xaml" ],
"poprawna": 0
},
{
"id": 22,
"tresc": "Która właściwość kontroluje widoczność elementu w MAUI?",
"odpowiedzi": [ "IsVisible", "Visible", "Show", "Opacity (zawsze)" ],
"poprawna": 0
},
{
"id": 23,
"tresc": "Jak nazywa się kontrolka przełącznika (tak/nie)?",
"odpowiedzi": [ "Switch", "Toggle", "Check", "Flip" ],
"poprawna": 0
},
{
"id": 24,
"tresc": "Która kontrolka służy do wyboru z listy rozwijanej?",
"odpowiedzi": [ "Picker", "Slider", "ImageButton", "BoxView" ],
"poprawna": 0
},
{
"id": 25,
"tresc": "Co robi FileSystem.OpenAppPackageFileAsync(...) w MAUI?",
"odpowiedzi": [ "Otwiera plik z internetu", "Otwiera plik dołączony do aplikacji", "Tworzy nowy plik na dysku", "Kasuje plik" ],
"poprawna": 1
},
{
"id": 26,
"tresc": "Jak w MAUI ustawić margines wokół kontrolki?",
"odpowiedzi": [ "Margin", "Padding (zawsze)", "Border", "Spacing" ],
"poprawna": 0
},
{
"id": 27,
"tresc": "Która właściwość w Button ustawia tekst na przycisku?",
"odpowiedzi": [ "Text", "Title", "Caption", "Value" ],
"poprawna": 0
},
{
"id": 28,
"tresc": "Jak w Shell cofnąć się do poprzedniej strony?",
"odpowiedzi": [ "Shell.Current.GoToAsync(\"..\");", "Navigation.PopAsync()", "App.Back()", "Window.Close()" ],
"poprawna": 0
},
{
"id": 29,
"tresc": "Co oznacza, że metoda jest 'async'?",
"odpowiedzi": [ "Może zawierać await", "Zawsze zwraca int", "Nie może mieć parametrów", "Nigdy nie kończy pracy" ],
"poprawna": 0
},
{
"id": 30,
"tresc": "Która kontrolka pokazuje obraz w MAUI?",
"odpowiedzi": [ "Image", "Label", "Border", "Entry" ],
"poprawna": 0
},
{
"id": 31,
"tresc": "Gdzie najczęściej trzymasz kolory, style i zasoby aplikacji?",
"odpowiedzi": [ "App.xaml", "MainPage.xaml.cs", "Program.cs", "launchSettings.json" ],
"poprawna": 0
},
{
"id": 32,
"tresc": "Co jest lepsze do wielu ekranów z menu: Shell czy NavigationPage?",
"odpowiedzi": [ "Shell", "NavigationPage", "Oba zawsze identyczne", "Żadne" ],
"poprawna": 0
},
{
"id": 33,
"tresc": "Który wyjątek często występuje przy złej konwersji string→int, jeśli nie użyjesz TryParse?",
"odpowiedzi": [ "FormatException", "NullReferenceException", "IndexOutOfRangeException", "DivideByZeroException" ],
"poprawna": 0
},
{
"id": 34,
"tresc": "Jaką właściwością ustawisz odstęp między elementami w VerticalStackLayout?",
"odpowiedzi": [ "Spacing", "Margin", "Padding", "Gap" ],
"poprawna": 0
},
{
"id": 35,
"tresc": "Co jest prawdą o JSON?",
"odpowiedzi": [ "To format danych tekstowych", "To baza danych", "To język programowania", "To plik EXE" ],
"poprawna": 0
},
{
"id": 36,
"tresc": "Jak w C# wczytać JSON do obiektów (najprościej)?",
"odpowiedzi": [ "System.Text.Json.JsonSerializer.Deserialize", "Console.ReadKey", "Directory.ReadAllText", "Bitmap.Load" ],
"poprawna": 0
},
{
"id": 37,
"tresc": "Który layout najłatwiej daje dwie kolumny (np. etykieta + pole)?",
"odpowiedzi": [ "Grid", "VerticalStackLayout", "ScrollView", "Border" ],
"poprawna": 0
},
{
"id": 38,
"tresc": "Co robi Title w ContentPage?",
"odpowiedzi": [ "Ustawia tytuł strony w pasku", "Ustawia kolor tła", "Ustawia rozmiar okna", "Ustawia czcionkę globalnie" ],
"poprawna": 0
},
{
"id": 39,
"tresc": "Dlaczego w aplikacji mobilnej nie robi się pętli while do obsługi pytań w UI?",
"odpowiedzi": [ "Bo zablokuje interfejs", "Bo while nie działa w C#", "Bo nie ma zmiennych", "Bo JSON tego zabrania" ],
"poprawna": 0
},
{
"id": 40,
"tresc": "Która instrukcja losuje elementy listy w C# (najprościej) przez sortowanie po Guid?",
"odpowiedzi": [ "OrderBy(x => Guid.NewGuid())", "SortAscending()", "Randomize()", "ShuffleAll()" ],
"poprawna": 0
}
]
}
App.xaml
App.xaml.cs
using Microsoft.Extensions.DependencyInjection;
namespace TestAplikacjeMobilne
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell(); // <- MUSI BYĆ AppShell
}
}
}
AppShell.xaml.cs
namespace TestAplikacjeMobilne;
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute(nameof(PodsumowaniePage), typeof(PodsumowaniePage));
}
}
AppShell.xaml
<?xml version="1.0" encoding="utf-8" ?>
<Shell
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TestAplikacjeMobilne"
x:Class="TestAplikacjeMobilne.AppShell"
FlyoutBehavior="Flyout">
<FlyoutItem Title="Test App Mob">
<ShellContent
Title="Test App Mob"
ContentTemplate="{DataTemplate local:MainPage}" />
</FlyoutItem>
</Shell>