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.
Brak komentarzy:
Prześlij komentarz