piątek, 29 października 2010

Prosty wrapper Messengera z MVVM Light Toolkit

Od razu zaznaczam, że pomysł nie jest mój, ale jest zaczerpnięty z przykładowego projektu na CodeProject. Jednak spodobał mi się bardzo, bo wprowadza porządek do projektu, jest prosty i oczywisty. 

W czym problem?
Podczas korzystania z Messengera, problemem okazało się tworzenie unikalnych tokenów za pomocą, których można by jednoznacznie identyfikować komunikaty. Dzięki tokenom można przesyłać wiele komunikatów tego samego typu (na przykład w prostym przypadku string'ów) i jednocześnie móc je od siebie odróżnić. Pozwala to także uniknąć konieczności tworzenia własnych klas komunikatów nawet dla prostych przypadków. 
Używanie ciągów znakowych wpisywanych z palca jako tokenów (na przykład "SelectedDirectoryChanged") jest niewygodne, podatne na literówki i nie daje żadnego wsparcia dla IntelliSense'a. Chciałem rozwiązać ten problem przez przechowywanie takich tekstowych tokenów w zasobach, ale okazało się to niewygodne i kłopotliwe. 

Rozwiązanie
Aby poradzić sobie z tym problem i jeszcze dodatkowo za darmo otrzymać wygodniejszy sposób posługiwania się komunikatami należy stworzyć statyczną klasę (AppMessages), która będzie wrapperem na Messengera. Najpierw deklarujemy enum'a, który będzie przechowywał wszystkie typy komunikatów jakie będziemy przesyłać:
enum MessageTypes
{
 SelectedDirectoryChanged,
 PhotoSourceChanged,
 AddPhoto, 
 SavePhoto
}
Następnie dla każdego komunikatu należy stworzyć osobną statyczną klasę wewnątrz AppMessages, która będzie posiadać dwie funkcje Send oraz Register. Ich jedynym zadaniem jest wywołanie odpowiednich funkcji Messengera, np:
public static class SelectedDirectoryMessage
{
 public static void Send(Models.DirectoryItem item)
 {
  Messenger.Default.Send<models.directoryitem>(item, MessageTypes.SelectedDirectoryChanged);
 }

 public static void Register(object recipient, Action<models.directoryitem> action)
 {
  Messenger.Default.Register<models.directoryitem>(recipient, MessageTypes.SelectedDirectoryChanged, action);
 }
}
Dzięki takiemu podejściu zyskujemy bardzo prosty i czytelny sposób obsługi komunikatów:
//rejestrowanie akcji reagującej na komunikat
AppMessages.SelectedDirectoryMessage.Register(this,
 item =>
 {
  this.CurrentDirectory = item.Path;
 });
 
//wysyłanie komunikatu
AppMessages.SelectedDirectoryMessage.Send(_fileitem);
Unikamy wpisywania za każdym razem tasiemców, w których trzeba podawać typy przesyłanych danych, robimy to tylko raz pisząc klasę obsługi komunikatu. 

Snippet do generowania klas obsługi komunikatów
Tworzenie nowych klas dla nowych komunikatów jest wtórne i powtarzalne, dlatego zrobiłem sobie snippet który generuje nową klasę i pozwala wpisać wszystkie parametry. Nie ma w nim nic nadzwyczajnego, ale pomimo tego jakby ktoś chciał go użyć wrzuciłem go tutaj.

czwartek, 7 października 2010

DirectoryTreeView - nowe funkcje

Do kontrolki którą stworzyłem w poprzednim poście dodałem nowe funkcjonalności, które wcześniej zaplanowałem, a mianowicie wystawianie na zewnątrz kontrolki aktualnie wybranego katalogu, możliwość wskazania zadanego katalogu oraz ukrywanie niedostępnych katalogów.

Aktualnie wybrany katalog
Jest to miejsce, w którym moja aktualna wiedza zmusza mnie do złamania zasada wzorca MVVM. Chciałem mieć możliwość bindowania aktualnego katalogu do innych kontrolek. Nie udało mi się udostępnić odpowiedniego Dependency Property w obiekcie DirectoryTreeViewModel, ponieważ nie dziedziczy ona po DependencyObject, dlatego nie udostępnia metod GetValue oraz SetValue potrzebnych do implementacji. Dlatego storzyłem DependencyProperty typu string w CodeBehind kontrolki, to znaczy w pliku DirectoryTreeView.xaml.cs:
public string CurrentDirectory
{
 get { return (string)GetValue(CurrentDirectoryProperty); }
 set { SetValue(CurrentDirectoryProperty, value); }
}

public static readonly DependencyProperty CurrentDirectoryProperty =
 DependencyProperty.Register("CurrentDirectory", typeof(string), typeof(DirectoryTreeView), new UIPropertyMetadata(""));
Natomiast aktualizację aktualnie wybranego folderu zrealizowałem poprzez wykorzystanie mechanizmu komunikatów z MVMM Light Toolkit. W obiekcie DirectoryItemViewModel, który przypisany jest jako DataContext dla każdego obiektu TreeViemItem w drzewku katalogów, podczas aktualizacji właściwości IsSelected na wartość true zostaje wysłany komunikat o zmianę wartości własności. Nie tworzę własnego typu komunikatów, jedynie wysyłam komunikat typu DirectotyItem z tokenem, którym jest po prostu string zawierający treść "SelectedDirectoryItemChangedToken". Abym nie musiał w różnych miejscach wpisywać tego ciągu znaków z palca, umieściłem go w zasobach projektu. Całość sprowadza się do dodania we właściwości IsSelected, w sekcji set następującego kodu:
if (_isSelected)
 Messenger.Default.Send<DirectoryItem>(_fileitem, 
  Chupacabra.Controls.Properties.Resources.SelectedDirectoryItemChangedToken);
Teraz należy gdzieś odebrać wysłany komunikat i przypisać go do Dependency Property. I tu niestety następuje pogwałcenie zasad MVVM. Kod odpowiedzialny za to przechwycenie umieszczam w Code Behind kontrolki, a dokładniej w konstruktorze:
Messenger.Default.Register<DirectoryItem>(this, Properties.Resources.SelectedDirectoryItemChangedToken,
 item =>
 {
  this.CurrentDirectory = item.Path;
 });
Teraz możliwe jest zbindowanie właściwości CurrentDirectory do dowolnych elementów z poziomu XAML'a. Na przykład można do projektu dodać pole tekstowe na bieżąco pokazujące wybrany folder:
<my:DirectoryTreeView Name="directoryTreeView"/>
<TextBlock Text="{Binding CurrentDirectory, ElementName=directoryTreeView}" />

Rozwijanie drzewka do wybranego katalogu
Wydaje mi się, że ta opcja jest konieczna w tego typu kontrolce. Chodzi o to aby móc rozkazać kontrolce rozwinąć drzewo katalogów do określonego folderu i na koniec go zaznaczyć jako aktualny. Aby wykonać tę operację należy za pomocą obiektu Messenger wysłać komunikat nowego typu stworzonego specjalnie na potrzeby kontrolki:
public class OpenPathMessage : MessageBase
{
 public string Path { get; set; }

 public OpenPathMessage(string _path)
 {
  Path = _path;
 }
}
Jedyna specyficzna informacja do przekazania to pełna ścieżka do folderu jaki chcemy otworzyć. Można to osiągnąć na przykład następującą linijką:
Messenger.Default.Send<Controls.Messages.OpenPathMessage>(
 new Controls.Messages.OpenPathMessage(@"D:\Programy\WTW"));
W konstruktorze DirectoryTreeViewModel można odebrać ten komunikat przez:
Messenger.Default.Register<Messages.OpenPathMessage>(this, openPath);
W funkcji openPath należy przejść przez wszystkie katalogi na jakie składa się pełna ścieżka, ustawić dla każdego elementu właściwość IsExpanded na true, żeby ewentualnie wczytać listę katalogów. 
private void openPath(Chupacabra.Controls.Messages.OpenPathMessage m)
{
 string path = m.Path;
 string[] split = path.Split(new char[] { '\\' });
 var divm = findRootViewModel(split[0]);
 int i = 1;

 while (divm != null && i < split.Length)
 {
  divm.IsExpanded = true;
  divm = divm.FindViewModel(split[i++]);
  if (divm.Path == path)
   divm.IsSelected = true;
 }
}
Najpierw rozdzielam ścieżkę na poszczególne składowe i sprawdzam czy litera dysku (czyli pierwszy element z tablicy) znajduje się na liście, jeśli tak to zwracam odpowiadający mu obiekt ViewModel:
private DirectoryItemViewModel findRootViewModel(string name)
{
 foreach (var item in _root)
 {
  if (item.Name.Trim('\\') == name)
   return item;
 }
 return null;
}
Następnie iteruje przez pozostałe w ścieżce elementy i rozwijam kolejne gałęzie. Jeśli uda mi się osiągnąć zadaną ścieżkę to dodatkowo zaznaczam końcowy element jako aktualny. Funkcja zaimplementowana w DirectoryItemViewModel pozwalająca przeszukać wszystkich potomków:
public DirectoryItemViewModel FindViewModel(string path)
{
 foreach (var item in _children)
 {
  if (item._fileitem.Name.Trim('\\') == path)
   return item;
 }
 return null;
}
Pomijanie ukrytych folderów
Tak naprawdę jest to bardzo prosta funkcjonalność. Jedyne czego wymagała to zmienienia ciała funkcji loadChildren z DirectoryItemViewModel:
private void loadChildren()
{
 foreach (var s in Directory.GetDirectories(_fileitem.Path))
 {
  DirectoryInfo di = new DirectoryInfo(s);
  if ((di.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden)
  {

   DirectoryItem item = new DirectoryItem
   {
    Name = s.Substring(s.LastIndexOf(@"\") + 1),
    Path = s
   };
   _children.Add(new DirectoryItemViewModel(item));
  }
 }
} 

Zaktualizowane źródła można znaleźć w standardowym miejscu.

wtorek, 5 października 2010

Własna kontrolka do przeglądania katalogów

Jedną z funkcjonalności Chupacabry będzie możliwość przeglądania zdjęć znajdujących się na dysku i aby to osiągnąć konieczny jest mechanizm nawigowania pomiędzy katalogami. Oczywiście można znaleźć gotowe rozwiązania zaimplementowane przez kogoś, gotowe do wykorzystania w WPF'ie, ale pomyślałem, że stworzenie takiej kontrolki samemu będzie dobrym sprawdzianem oraz ćwiczeniem z wykorzystania samego WPF'a jak i MVVM Light Toolkit w praktyce.
Od razu napiszę, że nie wymyśliłem samemu jak to ładnie połączyć, tylko oparłem się na artykule z CodeProject i jedynie uprościłem go nieco oraz dostosowałem do wykorzystania z MVVM Light Toolkit.


DirectoryTreeView
Tak właśnie nazywać się będzie ta kontrolka. Możliwości jakie chciałbym zaimplementować to przeglądanie struktury dysku twardego i zwracanie na bieżąco aktualnie wybranego katalogu, możliwość przeskoczenia do zarządanego przez użytkownika folderu oraz opcje pozwalające na niepokazywanie na przykład ukrytych katalogów. Chciałbym, żeby kontrolka była w jak największym stopniu zgodna z zasadami MVVM.
Do rzeczy. Do reprezentowania drzewiastej struktury katalogów oczywistym wyborem jest kontrolka TreeView. Oczywiście mógłbym wykorzystać jej zdarzenia i reagować na każde rozwinięcie gałęzi wczytaniem nowej listy katalogów i dodaniem do listy obiektów TreeViewItem, ale nie o to chodzi w MVVM. Dlatego idea jest następująca: stworzenie obiektu ViewModel powiązanego z plikiem XAML kontrolki, który będzie przechowywał listę innych obiektów ViewModel, z których każdy reprezentuje pojedynczą gałąź ze struktury katalogów i sam odpowiada za wczytywanie swoich podkatalogów. Może to trochę skomplikowane, ale niedługo powinno się wyjaśnić.


Model
Nie wiem czy można jedną prostą klasę nazwać modelem, ale na potrzeby podbudowania swojego programistycznego ego tak właśnie zrobię. Modelem będzie to co chcemy właściwie od tej kontrolki się dowiedzieć, czyli pełna ścieżka do katalogu oraz sama nazwa katalogu. Można to zawrzeć w takiej o to trywialnej klasie:
public class DirectoryItem
{
 public string Name { get; set; }
 public string Path { get; set; }
}
Instancja tej klasy będzie dostępna w każdym ViewModelu reprezentującym jedną gałąź.


DirectoryTreeViewModel
Właściwie powinien się on nazywać DirectoryTreeViewViewModel, ale jakoś za bardzo kłuło mnie to w oczy. Sama klasa dziedziczy z ViewModelBase, aby móc wykorzystywać MVVM Light Toolkit. Instancja tej klasy będzie przypisana do właściwości DataContext widoku kontrolki. Wspomniana wcześniej lista obiektów reprezentujących pojedynczy katalog udostępniania jest przez właściwość:
public const string RootPropertyName = "Root";
private ObservableCollection _root = null;

public ObservableCollection Root
{
 get
 {
  return _root;
 }

 set
 {
  if (_root == value)
   return;

  _root = value;

  RaisePropertyChanged(RootPropertyName);
 }
}
W konstruktorze pobierana jest lista dostępnych dysków i dla każdego z nich tworzony jest nowy obiekt DirectoryItemViewModel oraz następnie dodawany do listy. Nic szczególnego się nie dzieje.
public DirectoryTreeViewModel()
{
 Root = new ObservableCollection<directoryitemviewmodel>(getRootDirectories());
}

private IList<directoryitemviewmodel> getRootDirectories()
{
 IList<directoryitemviewmodel> result = new List<directoryitemviewmodel>();

 foreach (var s in Directory.GetLogicalDrives())
 {
  DirectoryItem item = new DirectoryItem
  {
   Name = s,
   Path = s
  };

  result.Add(new DirectoryItemViewModel(item));
 }

 return result;
}
DirectoryItemViewModel
To tu dzieje się większość magii związanej z przeglądaniem katalogów. Instancje tych obiektów będą używane jako DataContext dla obiektów TreeViewItem. Za pomocą data bindingu przekazywać będą dane do wyświetlania, a także reagować na zmiany stanu kontrolki TreeView.
Każdy ViewModel tego typu przechowuje instancję "modelu" odpowiadający aktualnemu katalogowi oraz listę jego podfolderów w postaci listy innych obiektów DirectoryItemViewModel.
private ObservableCollection _children;
private DirectoryItem _fileitem;
Na potrzeby data bindingu istotne dane udostępniane są przez właściwości:
public string Name
{
 get { return _fileitem.Name; }
}

public ObservableCollection<directoryitemviewmodel> Children
{
 get { return _children; }
}
I teraz bardzo istotna kwestia. Wczytywanie wszystkich katalogów na dysku, aby zbudować odpowiednią hierachię nie ma najmniejszego sensu. Dlatego należy zastosować coś na kształt "lazy loadingu", to znaczy wczytywania katalogów tylko wtedy, gdy zarząda tego użytkownik. Nie należy też wczytywać listy katalogów za każdym razem, gdy rozwijana będzie gałąź. Można zastosować coś co w artukule z którego korzystałem zostało określone jako "dummy object" (być może pasuje tłumaczenie tego jako manekin). Jego jedynym przeznaczeniem jest reprezentowanie nic nie znaczącej gałęzi, dzięki czemu będzie można wykryć czy gałąź była już rozwijana oraz czy posiada jakieś elementy. Poprzez stworzenie statycznej instancji będzie on wspólny dla wszystkich obiektów:
readonly static DirectoryItemViewModel DummyChild = new DirectoryItemViewModel();
Klasa posiada też dwa konstruktory, jeden prywatny tylko na potrzeby powyższej linijki kodu, a drugi inicjalizujący właściwy obiekt.
private DirectoryItemViewModel()
{ }

public DirectoryItemViewModel(DirectoryItem fileItem)
{
 _fileitem = fileItem;
 _children = new ObservableCollection<DirectoryItemViewModel>(new List<DirectoryItemViewModel>());

 _children.Add(DummyChild);            
}
Jedyne co konstruktor robi to przypisuje otrzymaną intancję modelu i tworzy listę podkatalogów z jednym elementem manekinem, symbolizującym, że gałąź przypisana do bieżącego obiektu nie została jeszcze rozwinięta.
Teraz czas na dwie istotne właściwości, które będą zbindowane do właściwości obiektu TreeViewItem, mianowicie IsSelected oraz IsExpanded. Ich znaczenia są raczej oczywiste, mają wartość true, gdy odpowiednio gałąź jest zaznaczona lub rozwinięta. Właściwość IsSelected jest zwykłą właściwością typu bool, która podczas przypisywania wartości wywołuje RaisePropertyChanged. Natomiast w IsExpanded zawarta jest obsługa wczytywania kolejnych katalogów:
public const string IsExpandedPropertyName = "IsExpanded";
private bool _isExpanded = false;

public bool IsExpanded
{
 get
 {
  return _isExpanded;
 }

 set
 {
  if (_isExpanded == value)
   return;

  _isExpanded = value;

  RaisePropertyChanged(IsExpandedPropertyName);

  if (HasDummyChildren)
  {
   _children.Remove(DummyChild);
   loadChildren();
  }
 }
}
Po uaktualnieniu wartości, sprawdzam czy czasem dany węzeł nie posiada tylko jednego potomka, którym jest właśnie nasz dummy object. Jeśli tak jest to usuwany jest on z listy podkatalogów a na jego miejsce dodawane są nowe odpowiadające strukturze aktualnego katalogu:
public bool HasDummyChildren
{
 get
 {
  return (_children.Count == 1 && _children[0] == DummyChild);
 }
}

private void loadChildren()
{
 foreach (var s in Directory.GetDirectories(_fileitem.Path))
 {
  DirectoryItem item = new DirectoryItem
  {
   Name = s.Substring(s.LastIndexOf(@"\") + 1),
   Path = s
  };
  _children.Add(new DirectoryItemViewModel(item));
 }
}
 
DirectoryTreeView.xaml.cs
Jedyną operacją jaką na razie trzeba zrobić w code behind kontrolki to przypisanie do właściwości DataContext nowej instancji odpowiedniego obiektu ViewModel:
DataContext = new ViewModel.DirectoryTreeViewModel();

DirectoryTreeView.xaml
Teraz należy odpowiednio połączyć wszystkie elementy poprzez bindingi:

 
  
   
  
 
 
  
 

Głównym źródłem elementów od którego należy zacząć jest właściwość Root z DirectoryTreeViewModel. Kolejne elementy pobierane są z właściwości Children obiektu DirectoryItemViewModel. Każdy element jest zwykłym polem tekstowym, w którym wyświetlana jest nazwa katalogu; jednoczeście istnieje możliwość ładnego dopasowania wyglądu kontrolki dodając na przykład ikonki przy każdym folderze. 
Bardzo istotny jest fragment zbindowania właściwości IsExpanded oraz IsSelected dla każdego obiektu TreeViewItem. To właśnie dzięki dwustronnemu powiązaniu wszystkie zmiany następują automatycznie bez zwracania na to uwagi. Dodatkowo jeśli dany element jest wybrany to będzie on wypisany grubszą czcionką. 

Co dalej?
W następnej kolejności chcę się zająć udostępnianiem na zewnątrz kontrolki aktualnie zaznaczonego katalogu, sprawdzaniem czy dany folder ma podkatalogi by ewentualnie nie wyświetlać strzałki do rozwijania oraz możliwością niepokazywania ukrytych katalogów.


Zaktualizowane źródła projektu dostępne są na BitBucket.

niedziela, 3 października 2010

Tajemnicze komunikaty debuggera


Podczas ostatnich prac z WPF'em napotykałem na strasznie irytujące sytuacje kiedy po uruchomieniu aplikacji wraz z debuggerem otrzymywałem nic nie mówiące mi komunikaty. Dla przykładu:
An unhandled exception of type 'System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll

Additional information: 'Add value to collection of type 'System.Windows.Controls.UIElementCollection' threw an exception.' Line number '6' and line position '10'.

I bądź tu mądry człowieku! Poczułem się jakbym wracał do dawnych czasów pisania programów w C/C++ kiedy otrzymywałem tylko komunikat segmentation failure i program kończył działania. Moja irytacja tym faktem była na tyle duża, że przerywałem pracę i zajmowałem się czymś innym dla ukojenia skołatanych nerwów.
Oczywiście wystarczyło trochę poszukać na niezawodnym StackOverflow. Rozwiązanie jest niesłychanie proste. Wybieramy kolejno Debug -> Exceptions i zaznaczamy haczyk przy opcji Common Language Runtime Exceptions. Po tej zmianie dalej otrzymujemy wyjątki, jednak teraz mają one jakąkolwiek wartość. Poprzednio wspomniany wyjątek zamienia się na:
A first chance exception of type 'System.InvalidOperationException' occurred in PresentationFramework.dll

Additional information: Window must be the root of the tree. Cannot add Window as a child of Visual.

Dodatkowe informacje są na tyle pomocne, że od razu znalazłem oczywisty błąd na który nie zwracałem uwagi i wszystko wróciło do normy. Jestem jednak mocno zdziwiony czemu takie informacje nie są wyśtwietlana "out of the box".

piątek, 1 października 2010

Podstawy MVVM Light Toolkit [część 2]

Drugiej części posta o MVVM Light Toolkit chciałem przedstawić bardzo ciekawe funkcjonalności, mianowicie: EventToCommand oraz obiekt Messenger. Postaram się przedstawić je na przykładzie, który będzie rozwinięciem aplikacji z poprzedniego posta.

EventToCommand
Jak już wcześniej wspominałem, w kontrolkach WPF'a komendy podpinane są domyślnie do jednego z góry przewidzianego zdarzenia, na przykład dla przycisku jest to odpowiednik OnClick. Nie ma możliwości podłączenia komendy do innych zdarzeń, jednak tu przychodzi z pomocą MVVM Light Toolkit. Nie jest to co prawda część samego wzorca MVVM, ale jeden z helperów, jednak jest to niesłychanie pomocna funkcjonalność. 
Dzięki niej istnieje możliwość podłączenia komend z obiektu ViewModel do dowolnych zdarzeń kontrolki bez angażowania do tego jakiegokolwiek Code Behind, czyli nadal żyjemy w zgodzie z założeniami MVVM. W moim przykładzie pod zdarzenie LostFocus podepnę sprawdzenie czy podana przez użytkownika liczba jest wylosowanym sekretnym numerem, dlatego też nie będzie konieczne klikanie w przycisk "Check guess", ale wystarczy na przykład naciśnięcie klawisza TAB, by przeskoczyć do następnej kontrolki. 
Najłatwiej wykonać połączenie między zdarzeniem i komendą z poziomu Expression Blend'a. Przed rozpoczęciem konieczne jest dodanie do projektu referencji do bilbioteki GalaSoft.MvvmLight.Extras.WPF4.dll. Następnie w zakładce Assets w grupie Behaviors należy odnaleźć pozycję EventToCommand i przeciągnąć ją na kontrolkę do której zdarzeń chcemy podpinać komendy. Pojawi nam się nowa pozycja w hierarchii obiektów, której właściwości możemy edytować. 
Z listy wybieramy nazwę zdarzenia, które chcemy wykorzystać (w naszym przypadku jest to LostFocus), a następnie podpinamy do niego komendę. Do właściwości Command poprzez binding podpinamy komendę CheckGuess z ExampleViewModel, natomiast do Command Parameter właściwość Text z kontrolki txbUserGuess, jednocześnie pamiętając o konieczności wykorzystania konwertera wartości z typu string do int, który został stworzony w poprzednim poście.
Oczywiście to samo można wykonać z poziomu kodu XAML, oto fragment odpowiedzialny z wykorzystanie mechanizmu EventToCommand, który został wygenerowany przez Blend'a.

 
  
   
  
 

Wykorzystuje on zaimportowane przestrzenie nazw:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4" 
Znaczenie powyższego kodu jest bardzo intuicyjne: do istniejącego TextBox'a odpowiedzialnego za wprowadzanie przez użytkownika liczby podpinamy nowy event trigger (o pozostałych dwóch rozdzajach triggerów można na przykład poczytać tu). Jest to trigger reagujący na zajście zdarzenia LostFocus i cała magia MVVM Light Toolkit polega na tym, że za pomocą jednego znacznika możemy podpiąć komendę do tego triggera dokładnie w taki sam sposób jak wcześniej podpięliśmy ją do przycisku.
Dodanie kolejnego triggera to po prostu dopisanie dodatkowego znacznia i:EventTrigger. Dokładniej o triggerach w WPF'ie można poczytać w artykule.

Messenger
Z założenia obiekt Messenger został dodany do MVVM Light Toolkit jako narzędzie do wszechstronnej komunikacji i to nie tylko pomiędzy obiektami ViewModel, ale między dowolnymi klasami. Nie jest on częścią wzorca MVVM, ale jednym z helperów stworzonych przez autora.
Idea opiera się na statycznym obiekcie Messenger, który udostępnia mechanizmy do wysyłania komunikatów oraz rejestrowania akcji reagujących na konkretne komunikaty. Możliwych scenariuszy pracy z obiektem Messenger jest naprawdę sporo. Możemy wysyłać komunikaty prostych typów do wszystkich zarejestrowanych odbiorców, stworzyć własne typy specyfikowanych komunikatów, wysyłać komunikaty tylko do konkretnych obiektów lub oznaczać komunikaty tokenami pozwalającymi na ich rozróżnienie. Ciekawą możliwością jest także wysyłanie komunikatów zawierających funkcję zwrotną (callback), co pozwala na wygodną dwustronną komunikację. Opcji jest sporo, ja do tej pory próbowałem tylko kilku najprostszych, jednak mam nadzieję, że podczas dalszej pracy znajdę zastosowania także dla pozostałych funkcjonalności.
W przykładowej aplikacji umieszczę tylko najprostszy przypadek, aby pokazać ogólną ideę działania. Mianowicie stworzony zostanie nowy obiekt ViewModel, który będzie jakby panelem opcji programu. Jedyną opcją będzie maksymalny zakres z jakiego można losować sekretną liczbę. Po zatwierdzeniu zmian zaktualizowany zostanie maksymalny zakres liczb, a dzięki bindingowi zostanie także odświerzona informacja o aktualnym maksymalnym zakresie.
Zaczynamy od stworzenia nowej kotrolki użytkownika jako nowego widoku w aplikacji i stworzenia prostego interfejsu użytkownika (pominąłem nieistotne na razie fragmenty): 

    
        
            
            
        
        
Następnie dodajemy odpowiadający temu widokowi obiekt ViewModel o nazwie OptionsViewModel i dziedziczący oczywiście po ViewModelBase. Dodajemy jedną prostą komendę SaveOptions, która zostanie podpięta do przycisku:
private RelayCommand<int> _saveCommand;

public RelayCommand<int> SaveOptions
{
 get {
  if (_saveCommand == null)
   _saveCommand = new RelayCommand<int>(
    x =>
    {
     Messenger.Default.Send<ChangeOptionsMessage>(new ChangeOptionsMessage(x));
    });
  
  return _saveCommand; }
}

Jej jedynym przeznaczeniem jest przesłać dalej otrzymany argument za pomocą obiektu Messenger. Argument, który będzie ona otrzymywać wynika z bindingu samego przycisku i jest to po prostu zawartość pola tekstowego z maksymalną liczbą, odpowiednio przekonwertowany na liczbę całkowitą:
Przesyłany komunikat jest typu ChangeOptionsMessage, jest to nasz własny typ komunikatu, który możemy dostosować tak aby zawierał takie informacje jakie są nam potrzebne w aktualnym scenariuszu. Dla wygody dziedziczy on po typie MessageBase z MVVM Light Toolkit i zawiera jedną dodatkową właściwość przechowującą maksymalną liczbę do wylosowania.
public class ChangeOptionsMessage : MessageBase
{
 public int MaxRange { get; set; }

 public ChangeOptionsMessage(int _maxRange)
 {
  MaxRange = _maxRange;
 }
}
Teraz nie pozostaje nam nic innego jak odebrać wysłany komunikat w głównym oknie. W konstruktorze ExampleViewModel rejestrujemy akcję reagującą na wysłanie komunikatu typu ChangeOptionsMessage:
Messenger.Default.Register(this, HandleChangeOptions);
oraz piszemy prostą funkcję obsługującą komunikat:
private void HandleChangeOptions(Messages.ChangeOptionsMessage m)
{
 this.MaxRange = m.MaxRange;
 initGuess();
}
Funkcja initGuess() rozpoczyna nową turę zgadywania z już zmienionym zakresem.
Źródła z tym przykładem do pobrania stąd.


Krótkie podsumowanie
MVVM Light Toolkit jest bardzo wygodnym narzędziem, nie przeraża mnogością funkcjonalności, jest prosty i intuicyjny w użyciu oraz wprowadza sporo udogodnień podczas pracy (jak na przykład gotowe szablony i snippety). Muszę jednak przyznać, że jestem nieco rozczarowany brakiem dokumentacji z prawdziego zdarzenia. Jedyna na czym można się opierać to przykłady aplikacji stworzone przez autora oraz społeczności użytkowników na forum projektu oraz na StackOverflow. To dość spora wada, ale mam nadzieję, że to tylko początkowe problemy i znikną one po opanowaniu toolkita.