using System.Diagnostics; namespace ProstyStoper; // ============================================================================ // MainPage - jedyna strona aplikacji // ============================================================================ // Ta klasa to "code-behind" pliku MainPage.xaml - czyli logika C# powiązana // z konkretnym widokiem XAML. Metoda InitializeComponent() (wywoływana // w konstruktorze) jest generowana automatycznie przez kompilator na // podstawie MainPage.xaml i tworzy wszystkie kontrolki (Label, Button) // opisane w tamtym pliku, udostępniając je tutaj jako pola (np. EtykietaCzas, // PrzyciskStartStop - nazwy wzięte z atrybutów x:Name w XAML). // ============================================================================ public partial class MainPage : ContentPage { // Stopwatch to klasa z System.Diagnostics, wyspecjalizowana w precyzyjnym // mierzeniu upływającego czasu (wykorzystuje wewnętrznie sprzętowy licznik // wysokiej rozdzielczości). To ona jest "prawdziwym" źródłem czasu // stopera - odpowiada za logikę pomiaru (start/stop/reset/upływ czasu), // niezależnie od tego, jak często odświeżamy widok. private readonly Stopwatch _stoper = new(); // IDispatcherTimer to timer dostarczany przez samo MAUI (a nie przez // .NET ogólnie). Jego kluczowa zaleta: callback (Tick) wykonuje się // automatycznie na wątku UI, więc można w nim bezpiecznie zmieniać // tekst etykiety. Używamy go wyłącznie do ODŚWIEŻANIA WIDOKU (co 100 ms), // a nie do samego mierzenia czasu - to ważne rozróżnienie: Stopwatch // mierzy czas dokładnie, timer tylko "pyta" go co jakiś czas o wynik. private IDispatcherTimer? _timerOdswiezania; public MainPage() { InitializeComponent(); UtworzTimerOdswiezania(); } // Tworzy timer odpowiedzialny za cykliczne odświeżanie etykiety z czasem. // Timer NIE jest tutaj od razu uruchamiany (Start() następuje dopiero // po naciśnięciu przycisku Start) - w spoczynku aplikacja nie zużywa // niepotrzebnie zasobów. private void UtworzTimerOdswiezania() { _timerOdswiezania = Dispatcher.CreateTimer(); _timerOdswiezania.Interval = TimeSpan.FromMilliseconds(100); _timerOdswiezania.Tick += (_, _) => OdswiezEtykieteCzasu(); } // Pobiera z obiektu Stopwatch upływający czas (właściwość Elapsed typu // TimeSpan) i formatuje go do czytelnej postaci tekstowej. private void OdswiezEtykieteCzasu() { TimeSpan uplynelo = _stoper.Elapsed; // Format niestandardowy TimeSpan: mm = minuty (z zerem wiodącym), // ss = sekundy (z zerem wiodącym), f = pojedyncza cyfra ułamkowej // części sekundy (dziesiąte części sekundy). Znak "\" przed // dwukropkiem i kropką jest wymagany w formatach TimeSpan, aby // te znaki zostały potraktowane dosłownie, a nie jako część // specyfikatora formatu. EtykietaCzas.Text = uplynelo.ToString(@"mm\:ss\.f"); } // Obsługuje kliknięcie jedynego przycisku akcji, który pełni podwójną // rolę Start/Stop w zależności od aktualnego stanu stopera // (Stopwatch.IsRunning). Jest to klasyczny wzorzec przełącznika (toggle), // w którym etykieta i kolor przycisku informują użytkownika o akcji, // jaka zostanie wykonana po kolejnym kliknięciu. private void PrzyciskStartStop_Clicked(object? sender, EventArgs e) { if (_stoper.IsRunning) { // Stoper działa -> zatrzymujemy pomiar i odświeżanie widoku, // a przycisk wraca do stanu "START" (zielony). _stoper.Stop(); _timerOdswiezania?.Stop(); PrzyciskStartStop.Text = "START"; PrzyciskStartStop.BackgroundColor = (Color)Application.Current!.Resources["TloPrzyciskuStart"]; } else { // Stoper nie działa -> uruchamiamy pomiar (Stopwatch.Start() // wznawia zliczanie od aktualnej wartości, nie zeruje jej) // oraz timer odświeżania widoku, a przycisk zmienia się na // "STOP" (czerwony). _stoper.Start(); _timerOdswiezania?.Start(); PrzyciskStartStop.Text = "STOP"; PrzyciskStartStop.BackgroundColor = (Color)Application.Current!.Resources["TloPrzyciskuStop"]; } } // Obsługuje przycisk Reset: zeruje zmierzony czas (Stopwatch.Reset() // zatrzymuje pomiar I ustawia Elapsed z powrotem na zero) oraz // natychmiast odświeża etykietę, aby użytkownik od razu zobaczył 00:00.0 // zamiast czekać na kolejny "tick" timera. private void PrzyciskReset_Clicked(object? sender, EventArgs e) { _stoper.Reset(); _timerOdswiezania?.Stop(); OdswiezEtykieteCzasu(); PrzyciskStartStop.Text = "START"; PrzyciskStartStop.BackgroundColor = (Color)Application.Current!.Resources["TloPrzyciskuStart"]; } // OnDisappearing jest wywoływane automatycznie przez framework, gdy // strona znika z ekranu (np. użytkownik przełącza aplikację w tło). // Zatrzymujemy wtedy TYLKO timer odświeżania widoku - sam obiekt // Stopwatch nadal zlicza czas w tle, więc po powrocie do aplikacji // wynik pomiaru pozostaje poprawny. protected override void OnDisappearing() { base.OnDisappearing(); _timerOdswiezania?.Stop(); } // Gdy strona znów staje się widoczna, wznawiamy odświeżanie widoku, // ale tylko jeśli stoper faktycznie w tym momencie działa. protected override void OnAppearing() { base.OnAppearing(); if (_stoper.IsRunning) { _timerOdswiezania?.Start(); } } }