Dziś zajmiemy się obsługą przycisków wg wzorca MVVM będziemy bindować również wartości z
TextBoxów.
Jako Pierwszym najłatwiejszym przyciskiem, który obsłużymy jest Button Zamknij aplikację. W tym celu zapoznamy się z klasą RelayCommand:
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
}
Na pierwszy rzut oka może się to wydać skomplikowane, jednak warte uwagi są jedynie dwie metody Execute i CanExecute, a także konstruktor klasy. Jak widać konstruktor wymaga delegatów. Pierwszy argument ma być metodą, która nic nie zwraca i może przyjąć parametr typu object. Jak sama nazwa wskazuje w konstruktorze "_execute" - jest to metoda, która coś wykona w przypadku jak zostanie wykonana czynność czyli np naciśnięcie przycisku. Kolejny delegat to Predicate czyli metoda, która zwraca bool i może przyjąc parametr typu object przy pomocy tej metody będzie sprawdzany warunek czy metoda w delegacie _execute może w ogóle zostać wykonana. Jeśli skorzystamy z drugiego konstruktora czyli:
public RelayCommand(Action<object> execute) : this(execute, null) {}
od razu metoda _canexecute zwracać będzie wartość true czyli warunek zawsze będzie spełniony. Stwórzmy zatem nowy folder o nazwie "helperClasses" i umieścimy sobie tam w/w klasę.
Jako pierwsze zaimplementujmy metodę wykonawczą, która będzie wyłączała program podczas naciśnięcia przycisku:
private void CloseApplication(object obj)
{
Application.Current.Shutdown();
}
Następnie zaimplementujmy właściwość, która obsłuży przycisk. Zwróćmy uwagę na to, że xaml ma już wbudowaną obsługę interfejsu ICommand, której strukturę za chwilę przedstawię. a więc z poziomu MainWindowViewModel właściwość będzie wyglądała następująco:
private ICommand _shutdownApplicationCommand = null;
public ICommand ShutApplicationCommand
{
get
{
if (_shutdownApplicationCommand == null)
{
_shutdownApplicationCommand = new RelayCommand(
p => CloseApplication(null), null);
}
return _shutdownApplicationCommand;
}
}
Teraz Przejdźmy do MainWindow i zaimplementujmy obsługę przycisku "Zamknij Aplikację" w xaml:
<Button Name="CloseApp" Content="Zamknij aplikację" Height="40" Margin="4"
Command="{Binding ShutApplicationCommand,Source={StaticResource MainWindowViewM}}"/>
Gotowe. Zauważmy, że możemy zbindować właściwość znajdującą się w klasie MainWindowViewModel i po naciśnięciu uruchamia przypisaną do niej metodę.
Przejdźmy teraz do przycisku
"Usuń Pracownika". Zaczniemy od dodania metody
Remove w klasie
WorkerRepository:
public void Remove(Worker worker)
{
_studentList.Remove(_studentList.Single(x=>x.WorkerId == worker.WorkerId));
}
Tworzymy teraz kolejną klasę do obsługi nazwijmy ją
ObservableObject, która dziedziczy po INotifyPropertyChanged. Wewnątrz klasy znajduje się delegat INotifyPropertyChanged.PropertyChangedEventHandler, który będzie obsługiwał zdarzenie, gdy właściwość zostanie zmieniona:
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string ev)
{
PropertyChange(new PropertyChangedEventArgs(ev));
}
private void PropertyChange(PropertyChangedEventArgs ev)
{
if (PropertyChanged != null)
{
PropertyChanged(this, ev);
}
}
}
Przejdźmy teraz do MainWindowViewModel i stwórzmy właściwość, gdzie będzie przechowywana wartośc wybranego wiersza, dodajmy także pole ICommand, które obsłuży nasz przycisk a także 2 metody. Jedna z nich będzie sprawdzała, czy został zaznaczony któryś z wierzy do usunięcia (jeśli nie zostanie wybrany żaden z wierszy przycisk będzie nieaktywny). W drugiej metodzie będzie implementacja usunięcia wybranej wartości z listy:
public class MainWidnowViewModel : ObservableObject
{
//...
public WorkerRowViewModel SelectedWorkerRow { get; set; }
private ICommand _eraseRow = null;
public ICommand EraseRowCommand
{
get
{
if (_eraseRow == null)
{
_eraseRow = new RelayCommand(p => EraseSelectedRow(SelectedWorkerRow),
p=>CheckIsSelectedRow(SelectedWorkerRow));
}
return _eraseRow;
}
}
private void EraseSelectedRow(WorkerRowViewModel value)
{
_repository.Remove(new Worker {WorkerId = value.WorkerId});
OnPropertyChanged("WorkerList");
}
private bool CheckIsSelectedRow(WorkerRowViewModel workerRowViewModel)
{
if (workerRowViewModel == null)
{
return false;
}
return true;
}
//....
}
}
Metoda w powyższym kodzie OnPropertyChanged("WorkerList"); Umożliwia odświeżanie DataGrid, gdy zostanie usunięty którykolwiek z rekordów. Teraz Przejdźmy do widoku okna i zaimplementujemy przekazanie wartości z rekordu DataGrid'a do wspomnianej właściwości SelectedWorkerRow w MainWindowViewModel:
<DataGrid Name="DGrStudentsTable" Grid.Column="1" Margin="5" Grid.ColumnSpan="2"
CanUserAddRows="False" AutoGenerateColumns="False"
ItemsSource="{Binding Source={StaticResource MainWindowViewM}, Path=WorkerList}"
SelectedItem="{Binding Source={StaticResource MainWindowViewM},Path=SelectedWorkerRow, Mode=TwoWay}"
>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding WorkerId}"/>
<DataGridTextColumn Header="Temat" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Nazwisko" Binding="{Binding Surname}"/>
<DataGridTextColumn Header="Satus Pracownika" Binding="{Binding WorkerStatus}"/>
</DataGrid.Columns>
</DataGrid>
Teraz można usuwać rekordy, jednak, gdy nie wybierzemy żadnej z wierszy - przycisk nie będzie aktywny:
Przejdźmy do ostatniej sekcji tego wpisu - Przycisku - "Dodaj Pracownika".
Aby wypełnić wartościami - najlepiej byłoby przenieść nasz słownik, który do tej pory był w właściwości WorkerList do pola w Klasie.
public class MainWidnowViewModel : ObservableObject
{
private Dictionary<eStatus, string> eStatusDictionary = new Dictionary<eStatus, string>
{
{eStatus.CodeWriter,"Programista"},
{eStatus.TeamManager,"Zwierzchnik Drużyny"},
{eStatus.Tester, "Tester Programów"}
};
}
Dzięki temu obsłużymy oczywiście wcześniejszą właściwość WorkerList i dodatkową właściwość dla Combobox'a:
public WorkerRowViewModel SelectedWorkerRow { get; set; }
public List<string> WorkerStatusToString
{
get
{
return eStatusDictionary.Values.ToList();
}
}
}
Teraz przechodzimy do Combobox'a w MainWindow.xaml. I uzupełniamy o atrybut:
<ComboBox Name="CboxStatus" Style="{StaticResource CustomXBoxStyle}"
ItemsSource="{Binding Source={StaticResource MainWindowViewM},Path=WorkerStatusToString}"
IsSynchronizedWithCurrentItem="True"
/>
Kolejnym Etapem będzie obsługa ostatniego przycisku "Dodaj Pracownika" Przejdźmy więc do klasy WorkerRepository i stwórzmy nową metodę dodającą nowego pracownika. Kolejne Id pracownika musi być przydzielane do każdego nowo dodanego pracownika dołóżmy zatem jeszcze prywatną metodę generującą Id:
private static int ActualId;
private int GenereateId()
{
return ++ActualId;
}
I metoda dodająca pracownika do "Bazy Danych":
public void Add(Worker worker)
{
worker.WorkerId = GenereateId();
_studentList.Add(worker);
}
W klasie MainWindowViewModel, gdzie dodajemy Właściwości do obsługi TextBoxów: Imię, Nazwisko i Comboboxa - Status:
private string _nameValueText;
public string NameValueText
{
get
{
return _nameValueText;
}
set
{
_nameValueText = value;
OnPropertyChanged("NameValueText");
}
}
private string _surnameValueText;
public string SurnameValueText
{
get
{
return _surnameValueText;
}
set
{
_surnameValueText = value;
OnPropertyChanged("SurnameValueText");
}
}
private string _statusValueText;
public string StatusValueText
{
get
{
return _statusValueText;
}
set
{
_statusValueText = value;
OnPropertyChanged("StatusValueText");
}
}
Teraz w MainWindow.xaml Wprowadzamy Implementację odczytu tych właściwości z poziomu kontrolek. Warto zwrócić uwagę na to, że argument Mode ustawiamy na "TwoWay" co oznacza, że dane z kontroli będą odsyłane od strony użytkownika poprzez wpis do TextBoxa jak i również z poziomu źródła (zmazanie wartości po wprowadzeniu danych).
<TextBox Name="TxtName" Height="40" Width="120" Text="{Binding Source={StaticResource MainWindowViewM},
Path=NameValueText,Mode=TwoWay}" Style="{StaticResource CustomTextBoxStyle}"/>
<TextBox Name="TxtSurname" Height="40" Width="120" Style="{StaticResource CustomTextBoxStyle}"
Text="{Binding Source={StaticResource ResourceKey=MainWindowViewM},Path=SurnameValueText,Mode=TwoWay}"/>
<ComboBox Name="CboxStatus" Style="{StaticResource CustomXBoxStyle}"
ItemsSource="{Binding Source={StaticResource MainWindowViewM},
Path=WorkerStatusToString}" IsSynchronizedWithCurrentItem="True"
SelectedValue="{Binding Source={StaticResource MainWindowViewM},
Path=StatusValueText}"
/>
W Comboboxie można wyróżnić dwa atrybuty: Itemsource oraz SelectedValue. Itemsource określa skąd mają być pobierane dane wyświetlane w comboboxie. Wartości te muszą być podane w formie listy i również muszą być przekonwertowane na typ string. Stwórzmy teraz obsługę przycisku. Jak w przypadku innych buttonów musimy zastosować właściwość typu ICommand i przypisać do typu RelayCommand dwie metody - jedna sprawdzająca czy zostały wypełnione wskazane TextBoxy druga wykonująca procedurę wprowadzania wartości z kontrolek do listy _repository.
private ICommand _eraseRow = null;
public ICommand EraseRowCommand
{
get
{
if (_eraseRow == null)
{
_eraseRow = new RelayCommand(p => EraseSelectedRow(SelectedWorkerRow),
p => CheckIsSelectedRow(SelectedWorkerRow));
}
return _eraseRow;
}
}
private void EraseSelectedRow(WorkerRowViewModel value)
{
_repository.Remove(new Worker { WorkerId = value.WorkerId });
OnPropertyChanged("WorkerList");
}
private bool CheckIsSelectedRow(WorkerRowViewModel workerRowViewModel)
{
if (workerRowViewModel == null)
{
return false;
}
return true;
}
Zadanie wykonane. Wszytkie nasze kontrolki działają. Wpisanie nowego pracownika wymaga wypełnienia Textboxów oraz wskazanie statusu w comboboxie. Usunięcie pracownika wymaga wybranie go z listy. Zgodnie z wzorcem MVVM nie powinniśmy wprowadzać implementacji w tzw. CodeBehind MainWindow.xaml.cs i rzeczywiście pliku nie zostały wprowadzane żadne zmiany.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
Oto nasz gotowy projekt:
Pod linkiem znajduje się cały projekt z 3-częściowego kursu:
Brak komentarzy:
Prześlij komentarz