Na prezentacji pokazałem sposób tworzenia aplikacji WPF z wykorzystaniem Visual Studio, Blend i wzorca projektowego MVVM. Kod i slajdy będą na SkyDrive grupy. Dzisiaj chcę wam pokazać jak można wykonać podobną aplikacje która może nam posłużyć do „wymyślania” haseł na konta ;p
To do dzieła.
1 Tworzymy nowy projekt.
Nowy projekt utworzymy z wykorzystaniem Blenda. Jeżeli ktoś nie ma Blenda może śmiało to samo wykonać w Visual Studio. Uruchamiamy Blenda po czym w okienku które nam wyskoczyło naciskamy New Project. Alternatywnie możemy zrobić to samo przez File-> New Project. Wybieramy typ projektu jako WPF Application. Nazywamy nasz projekt Haslo, zapamiętujemy gdzie go zapisujemy i klikamy OK.
2 Projektujemy widok
Powinno ukazać się nam okno domyślnie utworzone przez Blenda. Będziemy dążyć aby wygląd naszej aplikacji był taki jak niżej.
Tworzyć nasz widok możemy na dwa sposoby, poprzez przeciąganie kontrolek(niektórzy mogą preferować ten sposób) lub przez deklaracje bezpośrednio w kodzie. Ja osobiście wolę ta druga metodę wiec jej będziemy używać. Aby zobaczyć nasz kod klikamy na View-> Active Document View-> Split View ( będziemy widzieć kod i wygląd naszej aplikacji ). Zabieramy się za tworzenie widoku. Zaczynamy od zdefiniowania w naszym Gridzie dwóch wierszy do których będziemy przypisywać kontrolki. Uzyskujemy to przez następujący kod:
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
Następnie dodajemy dwa StackPanele. Jeden przypisujemy do pierwszego wiersza, drugi do drugiego.
W pierwszym tworzymy Labela który będzie nam wyświetlać losowane liczby. Wielkość czcionki ustawiamy na 40, wyśrodkowujemy w pionie i poziomie. Jego zawartość(Content) musimy połączyć z właściwością ViewModel który będziemy tworzyć w dalszej części. Drugi StackPanel będzie troche bardziej złożony. W jego wnętrzu deklarujemy dwa kolejne StackPanele. W obu ustawiamy orientację na poziomą i wyrównujemy w poziomie na środek. Do tej pory nasz fragment powinien wyglądać następująco.
<StackPanel Grid.Row="0">
<Label Content="{Binding Path=Liczby,UpdateSourceTrigger=PropertyChanged}" FontSize="40" HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Row="1">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Button Command="{Binding Start}" Height="45" Content="Start" FontSize="25" Margin="0,0,10,0"/>
<Button Command="{Binding Stop}" Height="45" Content="Stop" FontSize="25" Margin="0,0,10,0"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Slider Value="{Binding Slider}" Width="200" Height="20" Margin="0,10,0,0" />
<Label Content="{Binding Path=SliderValue, UpdateSourceTrigger=PropertyChanged}" FontSize="18"/>
</StackPanel>
</StackPanel>
Postępujemy analogicznie jak w przypadku pierwszego. Wypełniamy drugi następującym kodem:
<StackPanel Grid.Row="1">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Button Command="{Binding Start}" Height="45" Content="Start" FontSize="25" Margin="0,0,10,0"/>
<Button Command="{Binding Stop}" Height="45" Content="Stop" FontSize="25" Margin="0,0,10,0"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Slider Value="{Binding Slider}" Width="200" Height="20" Margin="0,10,0,0" />
<Label Content="{Binding Path=SliderValue, UpdateSourceTrigger=PropertyChanged}" FontSize="18"/>
</StackPanel>
</StackPanel>
Przy przyciskach pojawila się nowa właściwość – Command.
Nasz widok powinien być już gotowy. Później będziemy musieli na chwile do niego wrócić alby połaczyć nasz ViewModel z przed chwila utworzonym widokiem. Budujemy nasz projekt (Ctrl+Shift+B)
3 Visual Studio
W tej części zajmiemy się logika naszej aplikacji. Otwieramy w Visual Studio projekt który wczesniej utworzyliśmy. Na początku zajmiemy się utworzeniem naszego ViewModelu później przystąpimy do klas które będą mu potrzebne. Tworzymy nowa klasę :
Nazywamy ją MainViewModel i zatwierdzamy. Nowo utworzona klasa musi implementować interfejs INotifyPropertyChanged. Do tego potrzebne będzie nam dodanie przestrzeni nazw (using System.ComponentModel) . Aby przyśpieszyć sobie prace Visual Studio zaimplementuje go za nas. Klikamy prawym przyciskiem myszki na przed chwila zadeklarowanej implementacji, wybieramy Implement Interface -> Implement Interface.
Dodajemy metodę która będzie wywoływana przy każdej zmianie jakiejkolwiek właściwości. Będzie nam to potrzebne aby nasz widok mógł się zorientować, że zmiana miała miejsce i należy jego dane zaktualizować.
public void PropertyChangedInvoker(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Teraz kolej nadeszła na utworzenie właściwości w naszej klasie, dodajemy następujący kod:
//wartosc suwaka w postaci double
private double _slider;
public double Slider
{
get { return _slider; }
set
{
_slider = value;
SliderValue = Convert.ToInt32(_slider);
}
}
//pozycja suwaka w postaci int
private int _sliderValue;
public int SliderValue
{
get { return _sliderValue; }
set
{
_sliderValue = value;
PropertyChangedInvoker("SliderValue");
}
}
//liczby które są wyświetlane w widoku
private string _liczby;
public string Liczby
{
get { return _liczby; }
set
{
_liczby = value;
PropertyChangedInvoker("Liczby");
}
}
//odpowiada za trzymanie informacji czy przcisk Stop został nacisniety
private bool _stopButtonClicked;
public bool StopButtonClicked
{
get { return _stopButtonClicked; }
set
{
_stopButtonClicked = value;
PropertyChangedInvoker("StopButtonClicked");
}
}
//odowiada za trzymanie informacji czy przycisk Stop jest aktywny
private bool _canStopButtonExecute;
public bool CanStopButtonExecute
{
get { return _canStopButtonExecute; }
set
{
_canStopButtonExecute = value;
PropertyChangedInvoker("CanStopButtonExecute");
}
}
Naszemu ViewModelowi dalej czegoś brakuje. W Widoku utworzyliśmy 2 przyciski. Teraz pokaże co zrobić żeby je trochę ożywić. Pierwszym zadaniem będzie utworzenie i klas które będą implementowały interface ICommand. Klasy będą nazywać się odpowiednio StartCommand i StopCommand. Sama implementacja wygląda dokładnie tak samo jak wcześniej pokazałem. Wcześniej jednak do każdej z nich musimy dodać następującą przestrzeń nazw - using System.Windows.Input; Po tym nasze klasy, w tym wypadku StartCommnad, powinny wyglądać tak:
class StartCommand : ICommand
{
public bool CanExecute(object parameter)
{
throw new NotImplementedException();
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
throw new NotImplementedException();
}
}
Klasa posiada dwie metody i jeden event. Pierwsza metoda(CanExecute) jest wywoływana gdy event (CanExecuteChanged) zostanie „wzniesiony”. Tak nasz widok sprawdza czy dana komenda może być wykonana.
Druga metoda (Execute) zawiera kod który będzie wywołany w przypadku gdy komenda ma zostać wykonana ( w naszym przypadku przez naciśniecie przycisku).
Musimy trochę rozbudować nasze klasy gdyż puste metody niewiele nam pomogą J. Pierwszym krokiem będzie utworzenie konstruktora i dodanie referencji do naszego ViewModelu dla naszych klas. Pozwoli nam to odwoływać się do jego właściwości.
private readonly MainViewModel _vm;
public StartCommand(MainViewModel viewModel)
{
_vm = viewModel;
}
Dodamy teraz trochę logiki do naszych metod. Rozbudujemy tez nasz event.
public bool CanExecute(object parameter)
{
return _vm.CanStopButtonExecute == false && _vm.SliderValue != 10 && _vm.SliderValue != 0;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_vm.CanStopButtonExecute = true;
System.Threading.ThreadPool.QueueUserWorkItem(_vm.Losowanie);
}
Wytłumaczenia wymagają dwie rzeczy. Pierwsza to co to jest losowanie. Losowanie to będzie metoda która będziemy jeszcze implementować w ViewModelu. Będzie ona odpowiedzialna za losowanie liczb. Drugą rzeczą dla niektórych może być tajemnicze wywołanie metody -System.Threading.ThreadPool.QueueUserWorkItem. Pozwala nam ono wykonać dana metodę w osobnym wątku które będzie „obliczać” swoje rzeczy niezależnie od głównego wątku naszej aplikacji. Więcej informacji znajdziecie we wcześniejszych wpisach z tamtego roku. Dlaczego tak ? Nie chcemy aby nad naszą aplikacja pojawiła się klepsydra :P Nie odpowiadałaby ona na nasze żądania.Teraz do rzeczy bo zostało nam trochę roboty jeszcze. Druga nasza klasa powinna wyglądać podobnie:
class StopCommand : ICommand
{
private readonly MainViewModel _vm;
public StopCommand(MainViewModel viewModel)
{
_vm = viewModel;
}
public bool CanExecute(object parameter)
{
return _vm.CanStopButtonExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_vm.StopButtonClicked = true;
}
}
Musimy teraz „podpiąć” dopiero co stworzone klasy do ViewModelu. Przechodzimy do niego i tworzymy kolejne dwie właściwości i w konstruktorze przypisujemy do nich nowe obiekty:
public ICommand Start { get; set; }
public ICommand Stop { get; set; }
public MainViewModel()
{
Start = new StartCommand(this);
Stop = new StopCommand(this);
}
Dokładnie do tych dwóch właściwości bindowaliśmy nasze przyciski poprzez Command.
Kolejnym krokiem ( obiecuje ze już nie wiele tego zostało ) jest utworzenie klasy która będzie nam losować liczby o danej długości jak i metoda o której wcześniej wspomniałem. Zaczniemy od klasy.
Tworzymy i nazywamy ja Losomat. Oto logika która jest w niej zawarta:
public static class Losomat
{
private const int max = 999999999;
private const int min = 100000000;
private static readonly Random Random = new Random();
public static string Losuj(int ilosc)
{
return
Random.Next(Convert.ToInt32(min / Math.Pow(10, 9 - ilosc)), Convert.ToInt32(max / Math.Pow(10, 9 - ilosc))).ToString();
}
}
Mam nadzieje że wszytko w niej jest jasne.
Weźmiemy się teraz za metodę. Przechodzimy do ViewModelu i dodajemy.
public void Losowanie(object param)
{
for (int i = 0; i < SliderValue; i++)
{
Liczby += "0";
}
for (int i = SliderValue; i > 0; i--)
{
while (!StopButtonClicked)
{
var temp = Losomat.Losuj(i);
Liczby = Liczby.Substring(0, SliderValue - i) + temp;
System.Threading.Thread.Sleep(10);
}
StopButtonClicked = false;
}
CanStopButtonExecute = false;
}
Rzeczą która może się przydać z tego kawałka kodu może być System.Threading.Thread.Sleep. Jest to metoda która usypia dany watek na podana ilość czasu. Czas podajemy w milisekundach.
Ostatnia rzeczą jaka musimy wykonać jest złączenie Widoku z ViewModelem. Przechodzimy do Widoku, przed Gridem dodajemy przestrzeń nazw i definiujemy zasób dla naszego okna.
xmlns:Haslo="clr-namespace:Haslo"
<Window.Resources>
<Haslo:MainViewModel x:Key="ViewModel"/>
</Window.Resources>
Łączymy nasz nowy zasób z Gridem poprzez DataContext:
<Grid x:Name="LayoutRoot" DataContext="{StaticResource ViewModel }" >
W ostateczność nasz początek kodu Widoku powinien wyglądać następująco :
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Haslo="clr-namespace:Haslo"
x:Class="Haslo.MainWindow"
x:Name="Window"
Title="Haslo"
Width="459" Height="204">
<Window.Resources>
<Haslo:MainViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid x:Name="LayoutRoot" DataContext="{StaticResource ViewModel }" >
Po tych wszystkich bojach możemy przystąpić do kompilacji naszego programu i cieszyć się jego możliwościami :D Kod z komentarzami będzie można pobrać z linku niżej.