Tworzenie nowego projektu
Na początek do naszej wcześniej utworzonej solucji dodamy nowy projekt. Podobnie jak to robiliśmy wcześniej, kliknij prawym przyciskiem myszy na nazwę solucji, następnie Add i New Project.
Wpisz w kontrolce na górze WPF i następnie wybierz WPF Application i koniecznie wybierz język C#, a nie Visual Basic.
Kliknij przycisk Next. Wpisz odpowiednią nazwę dla naszego projektu. Zgodnie z naszą wcześniej ustaloną konwencją może to być: „Calculator.WpfApp”.
Kliknij Next. Wybierz odpowiedni framework, w naszym przypadku będzie to .NET 5.0.
Przycisk Create i projekt zostanie dodany do naszej solucji.
Przygotowanie folderów pod MVVM
Możemy na początek zrobić 1 zmianę, ponieważ tak jak Ci wspomniałem wcześniej, tutaj będziemy stosować wzorzec MVVM, czyli Model View ViewModel. Dlatego też, ja zawsze lubię sobie dodać na początek odpowiednie foldery, żeby na poziomie solucji już oddzielić te wszystkie warstwy.
Dodaj proszę najpierw folder Views i folder ViewModels. Do folderu Views przeniosę od razu cały plik MainWindow.xaml, czyli widok główny.
Oprócz tego jeszcze w pliku App.xaml trzeba uwzględnić te zmiany, to znaczy fakt, że MainWindows.xaml znajduje się w folderze Views.
<Application x:Class= "Calculator.WpfApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Calculator.WpfApp"
StartupUri="Views\MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Pierwsze uruchomienie projektu
Jeżeli teraz uruchomię aplikację CTRL+F5, to wszystko powinno być ok. Wcześniej jeszcze ustaw nowy projekt WPF, który stworzyliśmy jako projekt startowy. Jeżeli nie wiesz jak to zrobić, to dla przypomnienia kliknij prawym przyciskiem myszy na nazwę projektu, a następnie Set as Startup Project. Uruchom aplikację i powinna tak wyglądać:
Tworzenie widoku w XAML
Widok główny naszej aplikacji będziemy tworzyć właśnie w MainWindow.xaml. Przejdź do tego pliku i zobacz, że ten widok będzie tworzony zupełnie inaczej, niż to robiliśmy wcześniej w windows forms. To znaczy też możesz sobie tutaj przeciągać kontrolki z toolboxa, jednak dużo lepszym rozwiązaniem jest pisanie w tym kodzie XAML i dodawanie tutaj nowych kontrolek. Dzięki temu mamy pełną kontrolę nad tym, co będzie tworzone. Mała wskazówka, jeżeli teraz nie widzisz tego okna głównego (czasem może być taki problem po przeniesieniu widoku głównego do innego folderu), to spróbuj uruchomić ponownie visual studio. Czasem zdarzają się takie bugi, wtedy zawsze pomaga restart visual studio.
Na początek w XAML możemy sobie zmienić tytuł naszej formatki na "Kalkulator":
<Window x:Class="Calculator.WpfApp.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:Calculator.WpfApp"
mc:Ignorable="d"
Title="Kalkulator" Height="450" Width="800">
<Grid>
</Grid>
</Window>
Także mamy tutaj już utworzonego Grida, jest to taka jakby siatka i możemy sobie tutaj ustawić dowolną ilość kolumn i dowolną ilość wierszy. My w naszej aplikacji będziemy potrzebowali 5 kolumn i 5 wierszy. Spróbujmy sobie zrobić siatkę o takich rozmiarach, czyli najpierw definiujemy 5 kolumn wewnątrz grida.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
</Grid>
I tak samo musimy zrobić z wierszami, potrzebujemy 5 takich wierszy.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
Także jak widzisz tutaj powstała taka siatka. Teraz będziemy sobie mogli w dowolnym miejscu ustawić na przykład dowolny przycisk czy dowolną inną kontrolkę.
Zacznijmy najpierw od dodania na samej górze tej siatki (Grid'a) nowego TextBox'a, tak samo, jak to robiliśmy w przypadku Windows Forms. Nasza aplikacja w WPF’ie będzie wyglądała bardzo podobnie. Czyli najpierw na górze będzie TextBox i następnie będą podobnie wyglądające przyciski. Także wpisz pod definicją kolumn i wierszy TextBox. Chcemy, żeby zajmował on wszystkie górne kolumny w 1 wierszu, także musimy ustawić ColumnSpan na 5. Dzięki temu będzie zajmował wszystkie 5 kolumn w 1 wierszu.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.ColumnSpan="5" />
</Grid>
W tym momencie TextBox powinien już się pojawić w widoku, chociaż w tym pliku MainWindow, może to jeszcze nie być widoczne, ponieważ nie widać go na tym białym tle. Jeżeli uruchomisz aplikację, to zauważysz, że faktycznie na samej górze będzie ten dodany TextBox.
Następnie musimy dodać wszystkie przyciski, także tutaj będzie z tym już trochę pracy. Zaczniemy od dodania przycisku z numerem "7", a chcemy go umieścić w tym miejscu:
Wpisujemy Grid.Row=1, możemy też od razu ustawić Content, czyli wyświetlany text, będzie to 7. Dzięki temu pojawi się ten przycisk w odpowiednim miejscu i z odpowiednim oznaczeniem.
Czyli jest to 1 wiersz i kolumna 0, pamiętając, że zawsze liczymy wiersze i kolumny od 0. Jeżeli nie ustawimy Grid.Row lub Grid.Column, to domyślnie to jest 0. Czyli w naszym przypadku nie definiowaliśmy kolumny, więc domyślnie kolumna jest 0. Czyli w przypadku TextBox’a nie musimy tego wpisywać, bo domyślnie są już takie wartości, jakich oczekiwaliśmy. Ważne, żebyś zapamiętał, że zawsze liczymy od 0, czyli tak naprawdę pierwszy wiersz to w kodzie będzie wiersz 0. Tak samo z kolumnami. Ok, czyli mamy już pierwszy przycisk i tak samo musimy zrobić z pozostałymi. Możemy sobie skopiować pierwszy przycisk i potrzebujemy takich przycisków aż 17. Każdy z nich musi być przypisany do odpowiedniej kolumny i odpowiedniego wiersza.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" />
<Button Grid.Row="1" Grid.Column="0" Content="7" />
<Button Grid.Row="1" Grid.Column="1" Content="8" />
<Button Grid.Row="1" Grid.Column="2" Content="9" />
<Button Grid.Row="2" Grid.Column="0" Content="4" />
<Button Grid.Row="2" Grid.Column="1" Content="5" />
<Button Grid.Row="2" Grid.Column="2" Content="6" />
<Button Grid.Row="3" Grid.Column="0" Content="1" />
<Button Grid.Row="3" Grid.Column="1" Content="2" />
<Button Grid.Row="3" Grid.Column="2" Content="3" />
<Button Grid.Row="4" Grid.Column="0" Content="0" />
<Button Grid.Row="4" Grid.Column="2" Content="," />
<Button Grid.Row="1" Grid.Column="3" Content="/" />
<Button Grid.Row="2" Grid.Column="3" Content="-" />
<Button Grid.Row="3" Grid.Column="3" Content="*" />
<Button Grid.Row="4" Grid.Column="3" Content="C" />
<Button Grid.Row="1" Grid.Column="4" Content="+" />
<Button Grid.Row="3" Grid.Column="4" Content="=" />
</Grid>
Tak wygląda aplikacji w tym momencie:
Dla "0" ustawimy Grid.ColumnSpan=2, tak aby cyfra 0 zajmowała dwie kolumny, tak samo, jak to robiliśmy w aplikacji Windows Forms.
<Button Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Content="0" />
Z kolei dla operacji dodawania oraz znaku równości ustawimy Grid.RowSpan=2, dzięki temu będą rozmieszczane na 2 wierszach.
<Button Grid.Row="1" Grid.Column="4" Grid.RowSpan="2" Content="+" />
<Button Grid.Row="3" Grid.Column="4" Grid.RowSpan="2" Content="=" />
Także tak wygląda nasz widok główny, możemy teraz uruchomić aplikację i zobaczyć jak to faktycznie będzie wyglądało.
Stylowanie aplikacji – MahApps.Metro
Nie jest to jeszcze idealny efekt, ale za chwilę jeszcze spróbujemy poprawić ten wygląd. Jest taka biblioteka MahApps.Metro, dzięki której możemy w szybki sposób poprawić wygląd naszej aplikacji, pokaże Ci teraz jak ją zainstalować. Wcześniej możesz też zobaczyć całą dokumentację tej biblioteki, która jest dostępna tutaj: dokumentacja MahApps.Metro. Jest tutaj napisane również jak krok po kroku zainstalować bibliotekę.
W visual studio na początek uruchom proszę NuGet Packages (prawy przycisk myszy na projekt i Manage Nuget Packages). Wpisz w wyszukiwarce MahApps.Metro i zainstaluj znaleziony pakiet.
Po chwili pakiet powinien zostać dodany do projektu i możemy przejść do kolejnych kroków z dokumentacji. Zgodnie z dokumentacją w App.xaml musimy dodać nowy słownik ResourceDictionary i ten plik będzie miał taką zawartość:
<Application x: Class ="Calculator.WpfApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Calculator.WpfApp"
StartupUri="Views\MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--MahApps.Metro resource dictionaries.Make sure that all file names are Case Sensitive! -->
<ResourceDictionary Source ="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<!--Theme setting -->
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Oczywiście komentarze z tego kodu możesz usunąć. Następnie musimy dodać nowy namespace do widoku, w którym chcemy zaktualizować widok, czyli na tę chwilę w MainWindow.xaml.
<Window x:Class= "Calculator.WpfApp.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:Calculator.WpfApp"
xmlns:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
mc:Ignorable="d"
Title="Kalkulator" Height="450" Width="800">
Trzeba również zmienić główny tag Window, zarówno otwierający, jak i zamykający na mah:MetroWindow. Czyli:
<mah:MetroWindow x:Class="Calculator.WpfApp.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:Calculator.WpfApp"
xmlns:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
mc:Ignorable="d"
Title="Kalkulator" Height="450" Width="800">
…
</mah:MetroWindow>
Na koniec musimy jeszcze dokonać jeszcze jednej zmiany w code behind MainWindow, czyli w pliku MainWindow.xaml.cs. Chcemy, żeby ta klasa dziedziczyła teraz po MetroWindow.
using MahApps.Metro.Controls;
namespace Calculator.WpfApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : MetroWindow
{
public MainWindow()
{
InitializeComponent();
}
}
}
Jeżeli będziesz chciał zmienić więcej widoków, to dla każdego musisz dokonać powyższych zmian. W tym momencie, jeżeli uruchomisz aplikację, to zobaczysz, że będzie ona już wyglądała zupełnie inaczej.
Możemy sobie również dopasować kolorystykę, to znaczy np.zmienić na ciemną. Wystarczy w App.xaml zamiast:
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
ustawić:
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Dark.Blue.xaml" />
Jeżeli teraz uruchomisz aplikację, to będzie ona wygląda w ten sposób:
Dodawanie własnych styli
Możemy jeszcze trochę dopasować style dla konkretnych kontrolek. W naszym przypadku wystarczy zmodyfikować style dla Button’ów i TextBox’a. Przydałoby się dodać może jeszcze jakieś marginesy, zmienić czcionkę i wtedy myślę, że będzie to już fajnie wyglądać. Także przejdź proszę znowu do MainWindow.xaml.
W WPF’ie możemy style zmieniać na różne sposoby. Możemy je dodać w osobnych plikach i to jest chyba najlepszy sposób, ale dla takiej małej aplikacji jak ta, jak najbardziej możemy sobie te style zdefiniować w tym naszym oknie głównym, wprowadzać je w resource’ach. Ewentualnie możemy też wprowadzać je bezpośrednio wewnątrz kontrolki, ale najlepiej będzie dodać ogólne style dla całego okna. Jeżeli będziesz pisał bardziej rozbudowane aplikacje, to wtedy możesz je umieszczać w osobnych plikach. Także dodamy w resource’ach style dla 2 kontrolek:
<mah:MetroWindow.Resources>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="FontSize" Value="26" />
<Setter Property="Margin" Value="5" />
</Style>
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="FontSize" Value="46" />
<Setter Property="Margin" Value="5" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="IsReadOnly" Value= "True" />
</Style>
</mah:MetroWindow.Resources>
Dla każdej kontrolki ustawiamy odpowiedni TargetType. Ustawiamy też, że bazujemy na obecnych stylach. Dodatkowo ustawiliśmy wartości dla odpowiednich właściwości. W przypadku przycisku ustawiliśmy rozmiar czcionki na 26 oraz marginesy na 5. Z kolei dla TextBox’ów ustawiliśmy czcionkę o rozmiarze 46, margines 5 z każdej strony. Wyrównanie tekstu do prawej strony i w pionie wyśrodkowanie, a także ustawiliśmy właściwość IsReadOnly na True, dzięki czemu nie będzie można tam nic wpisać. Tylko możliwe będzie dodanie znaku na podstawie kliknięcia odpowiedniego przycisku, ale to już zaprogramujemy w kolejnym artykule. Jak widać na podglądzie, od razu lepiej to wygląda. Dodajmy jeszcze właściwość Text do TextBox’a i przypiszmy jakąś wartość, dzięki czemu zobaczysz, jak to będzie się wyświetlać w aplikacji.
<TextBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" Text="12390" />
Gotowy widok stworzony w XAML
Wydaje mi się, że jest jak najbardziej w porządku. Oczywiście możemy też tutaj wprowadzać jakieś kolory, tak samo jak to robiliśmy w aplikacji WindowsForms, ale myślę, że taki widok jest jak najbardziej ok na tą chwilę. Na koniec usunę jeszcze te wartości testowe z TextBoxa, które przed chwilą na moment dodałem.
<TextBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" />
Cały kod
//MainWindow.xaml
<mah:MetroWindow x:Class="Calculator.WpfApp.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:Calculator.WpfApp"
xmlns:mah="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
mc:Ignorable="d"
Title="Kalkulator" Height="450" Width="800">
<mah:MetroWindow.Resources>
<Style TargetType ="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="FontSize" Value="26" />
<Setter Property="Margin" Value="5" />
</Style>
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="FontSize" Value="46" />
<Setter Property="Margin" Value="5" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="IsReadOnly" Value="True" />
</Style>
</mah:MetroWindow.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" />
<Button Grid.Row="1" Grid.Column="0" Content="7" />
<Button Grid.Row="1" Grid.Column="1" Content="8" />
<Button Grid.Row="1" Grid.Column="2" Content="9" />
<Button Grid.Row="2" Grid.Column="0" Content="4" />
<Button Grid.Row="2" Grid.Column="1" Content="5" />
<Button Grid.Row="2" Grid.Column="2" Content="6" />
<Button Grid.Row="3" Grid.Column="0" Content="1" />
<Button Grid.Row="3" Grid.Column="1" Content="2" />
<Button Grid.Row="3" Grid.Column="2" Content="3" />
<Button Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Content="0" />
<Button Grid.Row="4" Grid.Column="2" Content="," />
<Button Grid.Row="1" Grid.Column="3" Content="/" />
<Button Grid.Row="2" Grid.Column="3" Content="-" />
<Button Grid.Row="3" Grid.Column="3" Content="*" />
<Button Grid.Row="4" Grid.Column="3" Content="C" />
<Button Grid.Row="1" Grid.Column="4" Grid.RowSpan="2" Content="+" />
<Button Grid.Row="3" Grid.Column="4" Grid.RowSpan="2" Content="=" />
</Grid>
</mah:MetroWindow>
//MainWindow.xaml.cs
using MahApps.Metro.Controls;
namespace Calculator.WpfApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : MetroWindow
{
public MainWindow()
{
InitializeComponent();
}
}
}
//App.xaml
<Application x:Class="Calculator.WpfApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Calculator.WpfApp"
StartupUri="Views\MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- MahApps.Metro resource dictionaries.Make sure that all file names are Case Sensitive! -->
<ResourceDictionary Source = "pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<!-- Theme setting -->
<ResourceDictionary Source = "pack://application:,,,/MahApps.Metro;component/Styles/Themes/Dark.Blue.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
PODSUMOWANIE
Widok główny naszej aplikacji jest gotowy, w takim razie pozostaje nam jeszcze zaimplementować całą logikę, oczywiście trzymając się wszystkich zasad wzorca MVVM. Tym zajmiemy się w kolejnym materiale. Pokażę Ci, jak może wyglądać taka implementacja.
Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności. Zapisz się na darmowy newsletter, gdzie co tydzień dzielę się wartościowymi materiałami w szczególności dotyczącymi C# i platformy .NET (darmowy zapis – newsletter).
Poprzedni artykuł - Pierwsza Aplikacja Desktopowa Windows Forms w C# – Logika (2/2).
Następny artykuł - Pierwsza Aplikacja Desktopowa WPF w C# – Logika MVVM (2/2)