niedziela, 14 listopada 2010

“Daj się poznać” – zakończenie :/

15 tygodni konkursu minęło. Czas na małe podsumowanie prac i trochę przemyśleń. Udało mi się spełnić wymagania konkursowe i mój projekt bierze udział w głosowaniu. Pomimo tego nie jestem zadowolony z tego co udało mi się osiągnąć. Plany miałem wielkie, a wyszło jak zawsze. Nie udało mi się wykonać nawet zadowalającej części z zaplanowanych funkcjonalności, nie pisałem tak często jak chciałem na początku. Pomimo tych niepowodzeń absolutnie nie żałuję decyzji o wzięciu udziału w konkursie. Nauczyłem się dzięki niemu o wiele więcej, niż byłbym w zrobić bez takiej motywacji. Na początek przemyśleń chciałbym pożalić się co było złe i godne pożałowania.

Porażki

Najważniejsza i najbardziej bolesna porażka – nie udało się wykonać większości z zaplanowanych funkcji. Jedyne co na chwilę obecną udostępnia Chupacabra to przeglądanie zdjęć znajdujących się na dysku. Nie została wykorzystana baza danych razem z mapowaniami NHibernate’a, nie ma możliwości grupowania zdjęć w albumy, nie można edytować i retuszować zdjęć, nie ma mowy o funkcjach społecznościowych. Wybujałe plany przegrały z szarą rzeczywistością. Z brakiem czasu i nierzadko energii, z brakiem motywacji i przekonania, że ta praca ma sens, z prostymi, często trywialnymi błędami, które zatrzymywały postępy na kilka godzin i odbierały ochotę do dalszej pracy.

Zawiodło również regularne aktualizowanie bloga. Co prawda, udało się w sam raz wpasować w wymaganą regulaminem ilość postów, jednak pozostaje niedosyt. Planowałem pisać często i ciekawie. Zamiast tego posty wychodziły rzadko, a do tego niektóre były bardzo długie, co z pewnością odstraszyło wielu potencjalnych czytelników. Rozczarowałem się webowym edytorem Bloggera. Pisanie postów nie było intuicyjne i kilka razy formatowanie rozjechało się po wstawieniu kodów źródłowych. Na szczęście tego posta (i mam nadzieję, że także następne) piszę z Windows Live Writer, który spełnia zadanie znakomicie. Być może coś jeszcze o nim napiszę.

Nie do końca jestem także zadowolony ze swojego wkładu w pozostałe projekty. Początkowo planowałem na bieżąco śledzić poczynania innych uczestników, komentować ich posty i być może postarać się czasem pomóc. Niestety tu też nie wyszło idealnie, przez długi czas udawało mi się w miarę uważnie przeczytać większość nowych postów, ale pod koniec wiele artykułów pominąłem, nie wspominając już o aktywnym komentowaniu. Jestem pod wielkim podziwem niektórych projektów i dziękuję ich autorom za inspiracje i ciekawe materiały.

Sukcesy

Największym sukcesem zdecydowanie jest wiedza, którą zdobyłem dzięki uczestnictwu w konkursie. Wiedza, której najprawdopodobniej nie zdobyłbym, gdyby tego konkursu nie było. Krótkie podsumowanie czego udało mi się dowiedzieć w trakcie tych tygodni konkursu:

  • Mercurial – użycie go do zarządzania kodem projektu sprawiło, że zakochałem się w rozproszonych systemach kontroli wersji i teraz na każdym kroku go wykorzystuję, do notatek, sprawozdań i wszystkich innych kodów.
  • NHibernate – idea wszelakich ORMów również przypadła mi bardzo do gustu i mam zamiar pogłębić ten temat. Co prawda moja znajomość tego frameworka jest na razie mocno podstawowa, jednak najważniejsze już wiem i przejście do bardziej zaawansowanych spraw nie powinno być problemem
  • WPF i MVVM – to moja porażka wśród sukcesów. Poznałem podstawy WPF’a i wykorzystywałem wzorzec MVVM, jednak jeszcze nie czuję do końca wszystkich mechanizmów i zachowań. Potrzebuję przeczytać książkę o WPFie od podstaw i wszystko sobie usystematyzować.
  • Coderush Express – znakomite narzędzie, które z pewnością będę nadal stosował, aż do momentu kiedy będzie mnie stać na wykupienie pełnej wersji :)
  • NUnit – już od dawna zbierałem się do wykorzystania testów jednostkowych w projekcie i wreszcie nadszedł ten czas.

Dzięki konkursowi wreszcie odważyłem się założyć własnego bloga. To bardzo duży sukces. Szczególnie, gdy spojrzałem na statystyki (nie przyszłoby mi na myśl je sprawdzić, gdybym nie zauważył tego w podsumowaniach innych uczestników :D). Jak dla mnie były one wręcz niebywałe, nie spodziewałem się, że tyle osób odwiedzało tego bloga (co prawda nie znam statystyk jak długo pozostawali na stronie i ile osób faktycznie przeczytało artykuły). Znakomitą większość wejść zawdzięczam serwisowi dotnetomaniak, kilka z moich postów się tam znalazło i wygenerowały całkiem spory ruch. W sumie ponad 4200 wejść i kilka postów z ponad 300 odwiedzinami. I jako ciekawostka: po wpisaniu w Google hasła mvvm, mój post pojawia się na piątym miejscu :P. Dla mnie jest bardzo miłe zaskoczenie.

Co dalej?

Blog mam nadzieję nie umrze tuż po zakończeniu konkursu. Co prawda nie planuję już pisać o projekcie konkursowym, ale chciałbym dodawać posty o tym o czym ten blog ma być. O programowaniu w dotnecie. Chciałbym uczyć się nowych zagadnień. Może ASP.NET? Albo Silverlight? A może na początek ugruntowanie wiedzy z WPF’a? Coś się na pewno znajdzie. Dlatego też cieszyłbym się, gdyby wszyscy nie usunęli RSS’a z mojego bloga tuż po zakończeniu konkursu. Może uda mi się coś wartościowego napisać.

Podziękowania

Czas na podziękowania. Przede wszystkim należą się one Maćkowi Aniserowiczowi za zorganizowanie tego konkursu i za nakłonienie nie tylko mnie, ale i wielu innych uczestników, do pracy i samodoskonalenia. Ponadto jestem winny podziękowania wszystkim, którzy mnie przekonali, żebym wziął udział w konkursie, że jednak warto i nic przez to nie stracę. I oczywiście wszystkim, którzy tego bloga czytali i komentowali, podsyłając kilka ciekawych propozycji i sugestii.

Dziękuję i zapraszam do odwiedzania bloga w przyszłości.

niedziela, 7 listopada 2010

Przeglądanie zdjęć na dysku

Czas żeby wykorzystać napisaną w ostatnim poście kontrolkę do wyświetlania obrazków przedstawioną w poprzednim poście. Na razie wykorzystywana będzie jedynie podczas przeglądania struktury dysku. Sprzężenie między aktualnym katalogiem a zdjęciami również będzie tymczasowe.

Aby w prosty sposób móc przekazać listę zdjęć do kontrolki ImageListView wystawiona na zewnątrz niej zostanie jedna DependencyProperty typu ObservableCollecion<Photo>. Dzięki temu bindowanie może być zrobione z poziomu XAML'a. 
public ObservableCollection<Photo> Photos
{
 get
 {
  return (ObservableCollection<Photo>)GetValue(PhotosProperty);
 }
 set
 {
  SetValue(PhotosProperty, value);
 }
}

public static readonly DependencyProperty PhotosProperty = DependencyProperty.Register(
 PhotosPropertyName,
 typeof(ObservableCollection<Photo>),
 typeof(ImageListView),
 new PropertyMetadata(new ObservableCollection<Photo>(), new PropertyChangedCallback(OnPhotosListChanged)));
Przy każdej zmianie zawartości listy zdjęć zostanie wywołana funkcja OnPhotosListChanged. Jej jedynym zadaniem będzie wysłanie komunikatu poprzez niedawno stworzoną statyczną klasę AppMessages.
private static void OnPhotosListChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
 AppMessages.PhotoSourceChangedMessage.Send(e.NewValue as ObservableCollection<Photo>);
}
Wiadomość ta jest odbierana w konstruktorze kontrolki i po prostu przypisuje nowo otrzymaną listę to właściwości użytej do przechowywania listy:
AppMessages.PhotoSourceChangedMessage.Register(this,
 p => Photos = p);

Do przeglądania zdjęć na dysku potrzebne jest jedynie, aby lista zdjęć była odświeżona za każdym razem, gdy zmieni się zaznaczony folder. Można to osiągnąć przez zbindowanie aktualnie wybranego katalogu z kontrolki DirectoryTreeView do właściwości Photos kontrolki ImageListView. 
<my:ImageListView Photos="{Binding CurrentDirectory, Converter={StaticResource DirectoryToPhotosConverter}, ElementName=directoryTreeView}"/>
Oczywiście nie zrzucamy na kontrolkę wyświetlającą odpowiedzialności z pobranie listy zdjęć z dysku. Dlatego też użyty jest konwerter zamieniający ścieżkę do katalogu na listę obiektów Photo, wskazujących na zdjęcia z tego katalogu. 
[ValueConversion(typeof(string), typeof(ObservableCollection<Photo>))]
public class DirectoryToPhotosConverter : IValueConverter
{
 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
 {
  string str = value as string;

  if (str != null)
  {
   IList<Photo> photos = ImageLoader.GetListOfPhotosFromPath(str);
   if (photos != null)
    return new ObservableCollection<Photo>(photos);
  }

  return new ObservableCollection<Photo>();
 }

 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
 {
  throw new NotImplementedException();
 }
}
Konwerter ten wykorzystuje metodę GetListOfPhotosFromPath z klasy ImageLoader:
public class ImageLoader
{
 public static List<Photo> GetListOfPhotosFromPath(string path)
 {
  List<Photo> result = new List<Photo>();

  if (Directory.Exists(path))
  {
   result.AddRange(getPhotosByExt(path, "*.jpg"));
   result.AddRange(getPhotosByExt(path, "*.jpeg"));
   result.AddRange(getPhotosByExt(path, "*.png"));
   result.AddRange(getPhotosByExt(path, "*.gif"));
  }

  return result;
 }

 private static List<Photo> getPhotosByExt(string path, string ext)
 {
  List<Photo> result = new List<Photo>();

  string[] files = Directory.GetFiles(path, ext);
  foreach (var f in files)
  {
   result.Add(new Photo
   {
    Path = f,
    Description = Path.GetFileName(f)
   });
  }
  return result;
 }
}
Jest to trochę naiwna implementacja zakładająca, że istnieją tylko cztery rodzaje plików graficznych, ale to sprawdzenia poprawności działania w zupełności wystarcza.

Po połączeniu tych elementów można już przeglądać katalogi w poszukiwaniu zdjęć :P

piątek, 5 listopada 2010

Wyświetlanie listy zdjęć

Czas na kolejną kontrolkę, która jest jednym ze składników okna głównego. Mianowicie kontrolkę pozwalającą wyświetlać kolekcję zdjęć w najprostszej tabelarycznej formie. Może się ona przydać zarówno do wyświetlania zawartości albumów jak i do przeglądania struktury dysku i podglądu zdjęć z katalogów. 

Oprócz samego zdjęcia chciałbym wyświetlać także opis, jeśli taki jest przypisany do zdjęcia (jeśli nie ma to po prostu nazwę pliku). Dlatego też wygodnie będzie wykorzystać odpowiedni DataTemplate, który z listy obiektów typu Photo (dokładnie tego, który jest wykorzystywany jako jeden z modeli przy współpracy z NHibernate) utworzy szereg obrazów wraz z podpisem. Lista zdjęć przechowywana będzie w obiekcie ViewModel jako prosta właściwość implementująca IPropertyChanged:
private ObservableCollection<Photo> _photos = new ObservableCollection<Photo>();
public ObservableCollection<Photo> Photos
{
 get
 {
  return _photos;
 }

 set
 {
  if (_photos == value)
   return;

  _photos = value;

  // Update bindings, no broadcast
  RaisePropertyChanged(PhotosPropertyName);
 }
}
Na początek należy przygotować kontener, do którego zbindowane będą zdjęcia i wskazać mu szablon danych (który zostanie omówiony w następnej kolejności). 

 
  
   
    
   
  
 

Jako kontener wybrałem WrapPanel, ponieważ sam dobiera ilość wyświetlanych kolumn w zależności od dostępnego miejsca. 

Teraz pora na utworzenie szablonu itemsTemplate, który będzie definiował wygląd każdego elementu umieszczanego we WrapPanel'u. Pojedynczy element będzie składał się z obiektu Image, dla którego właściwość Source ustawiona będzie na ścieżkę dostępu do pliku ze zdjęciem oraz obiektu TextBlock wyświetlającego opis obrazka. Wewnątrz DataTemplate'u może być umieszczony tylko jeden element, dlatego też będą one opakowane w StackPanel. W uproszczeniu prezentuje się to następująco:

 
  
  
 

Dodatkowo chciałem, aby kliknięcie na zdjęcie albo podpis (ogólnie na cały StackPanel) powodowało podjęcie odpowiedniej akcji. Niestety okazało się to trudniejsze niż myślałem. Wszystkie elementy będące wewnątrz WrapPanel'u przejęły DataContext od właściwości ItemsSource z kontenera ItemsControl, czyli nie miały pojęcia o istnieniu ImageListViewModel i nie tym samym nie było możliwości wywołania odpowiedniej komendy. Rozwiązaniem okazało się zastosowanie bardzo rozbudowanego bindingu do obiektu EventToCommand pochodzącego z MVVM Light Toolkit:

 
  
   
   
  
 
 ...

Taki binding przeszukuje w górę hierarchię obiektów, aż do momentu znalezienia obiektu typu ItemsControl, wtedy wykorzystuje jego DataContext w celu wywołania komendy. Jako parametr przekazywany jest cały obiekt typu Photo przypisany do aktualnego elementu. Nie jest ona specjalnie skomplikowana, ma za zadanie jedynie przesłanie odpowiedniego komunikatu:
private RelayCommand<Photo> _onClickCommand = null;
public RelayCommand<Photo> OnClick
{
 get {
  if (_onClickCommand == null)
  {
   _onClickCommand = new RelayCommand<Photo>(
    p =>
    {
     AppMessages.PhotoSelectedChanged.Send(p);
    });
  }
  return _onClickCommand;
 }
}
Jest to bardzo podstawowy zakres funkcjonalności jakie spadają na tę kontrolkę. Nie mam do końca zdefiniowanej wizji co do ostatecznego połączenia kontrolek i ich funkcjonalności (na przykład na której powinien ciążyć obowiązek wyświetlania tagów i daty dodania). W zależności od tego co się wyklaruje być może ta kontrolka zostanie jeszcze rozszerzona.