Featured Image
Aug 22, 2010 2 min read 0 comments

[PL] struktura projektu

Obecna struktura projektu DotBeer przedstawia się następująco: W projekcie użyłem architektury warstwowej. Projekt został, więc podzielony na osobne, wyspecjalizowane warstwy. Każda warstwa została umieszczona w oddzielnym projekcie. Dzięki takiemu rozwiązaniu, zarządzanie projektem stało się znacznie łatwiejsze. Poniżej znajduje się opis poszczególnych projektów: DotBeer.Business.Components – część warstwy biznesowej, która zawiera logikę biznesową, DotBeer.Business.Entities – część warstwy biznesowej, która zawiera encje biznesowe. Projekt ten nie posiada referencji do pozostałych projektów zawierających poszczególne warstwy, DotBeer.Data – warstwa dostępu do danych. Obecnie dane zapisywane są do plików XML, które znajdują się w katalogu aplikacji. Takie rozwiązanie nie jest zalecane, ponieważ dane aplikacji powinny być przechowywane w lokalnym katalogu ustawień (Application Data) danego użytkownika. Planuje tutaj użycie Entity Framework w połączeniu z SQLite. Dzięki temu nie będę musiał samemu tworzyć warstwy dostępu do danych, tylko wykorzystam już gotowe rozwiązanie, DotBeer.UI.WPFClient – warstwa prezentacji stworzona zgodnie ze wzorcem Model-View-ViewModel. Obecnie warstwa korzysta z  WPF Model-View-ViewModel Toolkit – bardzo prostego toolkita od Microsoftu. Będę chciał tutaj wykorzystać jakąś bardziej zaawansowaną bibliotekę do tworzenia aplikacji zgodnie ze wzorcem MVVM. W swojej pracy inżynierskiej wykorzystałem bibliotekę Microsoft Prism. Sprawdziła się ona świetnie podczas tworzenia aplikacji desktopowej w WPF oraz aplikacji internetowej w Silverlight. Prawdopodobnie użyję właśnie tej biblioteki w projekcie DotBeer, DotBer.UI.WinFormsClient.Updater – projekt aplikacji Windows Forms, której zadaniem jest dokonanie aktualizacji DotBeer’a. Program aktualizujący jest ściągany z mojej strony domowej w momencie wykrycia nowej wersji programu DotBeer. O implementacji aktualizacji automatycznych będzie trochę więcej w późniejszych postach, DAnton.Blocks.Updater – projekt zawierający klasy odpowiedzialne za sprawdzenie dostępności aktualizacji jak i również pobrania i uruchomienia tej aktualizacji, DAnton.Blocks.Extensions – projekt zawierający tzw. method extensions, DAnton.Blocks.Helpers – projekt zawierający różne klasy pomocnicze. Trzy ostatnie projekty były stworzone z myślą o użyciu ich również w przypadku innych projektów . Stąd wziął się przedrostek DAnton.Blocks – chciałem stworzyć coś na styl bloków programistycznych z Enterprise Library ;). Ostatecznie projektów tych nie wykorzystałem przy tworzeniu żadnej innej aplikacji. Trochę z lenistwa i trochę z braku potrzeby. Myślę, że ten krótki opis struktury projektu przyda się tym, którzy grzebią przy źródłach DotBeer’a. Są tacy w ogóle? 😀

Featured Image
Aug 22, 2010 6 min read 2 comments

[PL] własny SettingsProvider

W .NET mamy do dyspozycji wygodny mechanizm do zapisywania ustawień aplikacji. Nie będę tutaj opisywać podstaw obsługi tego mechanizmu. Osoby niezaznajomione z tym mechanizmem odsyłam do dokumentacji. W tym wpisie skupię się na stworzeniu własnego dostawcy ustawień. Domyślnym i jedynym standardowo dostępnym dostawcą, który zajmuje się zapisem ustawień jest LocalFileSettingsProvider. Dostawca ten zapisuje pliki do lokalnego katalogu ustawień danego komputera. W systemie Windows 7 przykładowa ścieżka dla pliku ustawień ma następującą postać: C:\Users\Damian\AppData\Local\Microsoft\CustomSettingsProvider.ex_Url_sxc2govh1iyjc1j3np0gzhgzqqk3dctn\1.0.0.0\user.config Ścieżka do pliku nie wygląda zbyt pięknie. W tym wpisie możemy się dowiedzieć (pytanie “Why is the path so obscure? Is there any way to change/customize it?”), że algorytm generujący ścieżkę musiał zapewnić jej unikalność. Ja chciałbym jednak uzyskać następującą ścieżkę: C:\Users\Damian\AppData\Local\Microsoft\CustomSettingsProvider\user.config Prawda, że ładniej? Standardowo nie mamy żadnej możliwości określenia w jakiej lokacji ma być przechowywany plik ustawień. Jedyną możliwością jest stworzenie własnego dostawcy ustawień, który musi dziedziczyć po klasie SettingsProvider. Tworząc nowego dostawcę musimy zaimplementować przynajmniej trzy następujące metody: GetPropertyValues, SetPropertyValues i ApplicationName. Na początek implementacja właściwości ApplicationName: public override string ApplicationName { get { return EntryAssemblyHelper.ProductName; } set { } } Na jej potrzeby stworzyłem klasę pomocniczą EntryAssemlbyHelper: public static class EntryAssemblyHelper { #region properties public static string ProductName { get { return GetAttribute<AssemblyProductAttribute>().Product; } } public static string CompanyName { get { return GetAttribute<AssemblyCompanyAttribute>().Company; } } #endregion #region private methods private static T GetAttribute<T>() where T : class { return Assembly.GetEntryAssembly() .GetCustomAttributes(typeof(T), true) .First() as T; } #endregion } We właściwości ApplicationName zwracana jest więc nazwa aplikacji zapisana w Assembly, które zostało uruchomione na samym początku. Następnie należy zaimplementować metodę SetPropertyValues, która to wygląda następująco: public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection settings) { this.CreateRequiredFoldersAndFilesIfNeeded(); XDocument document; try { document = XDocument.Load(this.appSettingsFilePath); } catch { this.BackupSettingsFile(); document = new XDocument(); document.Add(new XElement("settings")); } XElement root = document.Element("settings"); string groupName = context["GroupName"].ToString(); XElement contextSettings = root.Element(groupName); if (contextSettings == null) { contextSettings = new XElement(groupName); root.Add(contextSettings); } IList<XElement> nodesToAdd = new List<XElement>(); foreach (SettingsPropertyValue property in settings) { bool saveWithoutDefault = false; XElement setting = contextSettings.Elements() .Where(element => element.Name == "setting" && element.HasAttributes && element.FirstAttribute.Name == "name" && element.FirstAttribute.Value == property.Name && element.HasElements) .FirstOrDefault(); if (setting != null) { XElement defaultValue = setting.Element("defaultValue"); if (defaultValue != null) { setting = new XElement("setting", new XAttribute("name", property.Name), new XElement("value", property.SerializedValue), new XElement("defaultValue", defaultValue.Value)); } else { saveWithoutDefault = true; } } else { saveWithoutDefault = true; } if (saveWithoutDefault) { setting = new XElement("setting", new XAttribute("name", property.Name), new XElement("value", property.SerializedValue)); } nodesToAdd.Add(setting); } contextSettings.RemoveAll(); foreach (XElement node in nodesToAdd) contextSettings.Add(node); document.Save(this.appSettingsFilePath); } Parametr “context” typu SettingsContext(klasa dziedziczy po Hashtable) zawiera informacje na temat pliku “settings” w jakim zostały stworzone nasze ustawienia. Podglądając tą zmienną podczas debugowania możemy zobaczyć np. takie informacje: Nas interesuje klucz “GroupName”, który zawiera nazwę i przestrzeń nazw w jakiej znajduje się plik ustawień. Implementacja metdy SetPropertyValues używa LINQ to XML do zapisu ustawień. Wynikiem działania tej metody będzie plik XML o następującej strukturze: <?xml version="1.0" encoding="utf-8"?> <settings> <CustomSettingsProvider.FavoriteSettings> <setting name="FavoriteActor"> <value>Clint Eastwood</value> <defaultValue>Edward Norton</defaultValue> </setting> <setting name="FavoriteMovie"> <value>The Good, the Bad and the Ugly</value> <defaultValue>Fight Club</defaultValue> </setting> </CustomSettingsProvider.FavoriteSettings> <CustomSettingsProvider.HouseSettings> <setting name="DoorsColor"> <value>Gold</value> <defaultValue>Blue</defaultValue> </setting> </CustomSettingsProvider.HouseSettings> </settings> Zostały tutaj zapisane dwa węzły “CustomSettingsProvider.FavoriteSettings” oraz “CustomSettingsProvider.HouseSettings”, ponieważ w przykładowym projekcie stworzyłem dwa pliki typu “settings”. Kolej na implementację metody GetPropertyValues, która będzie odczytywać zapisane ustawienia: public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection settingsCollection) { XDocument document; try { document = XDocument.Load(this.appSettingsFilePath); } catch { document = new XDocument(); } XElement root = document.Element("settings"); if (root == null) { root = new XElement("settings"); document.Add(root); } string groupName = context["GroupName"].ToString(); XElement contextSettings = root.Element(groupName); if (contextSettings == null) { contextSettings = new XElement(groupName); root.Add(contextSettings); } SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection(); foreach (SettingsProperty property in settingsCollection) { XElement setting = contextSettings.Elements() .Where(element => element.Name == "setting" && element.HasAttributes && element.FirstAttribute.Name == "name" && element.FirstAttribute.Value == property.Name && element.HasElements) .FirstOrDefault(); if (setting != null) { XElement propertyValue = setting.Element("value"); settings.Add(new SettingsPropertyValue(property) { SerializedValue = propertyValue.Value }); XElement defaultPropertyValue = setting.Element("defaultValue"); if (defaultPropertyValue == null) { defaultPropertyValue = new XElement("defaultValue"); setting.Add(defaultPropertyValue); } defaultPropertyValue.Value = property.DefaultValue.ToString(); } else { XElement propertySetting = new XElement("setting", new XAttribute("name", property.Name), new XElement("value", property.DefaultValue), new XElement("defaultValue", property.DefaultValue)); contextSettings.Add(propertySetting); settings.Add(new SettingsPropertyValue(property) { SerializedValue = property.DefaultValue }); } } document.Save(this.appSettingsFilePath); return settings; } W przypadku braku pliku lub braku informacji o ustawieniach w pliku, zostaną zwrócone wartości domyślne. Również i w tej metodzie zostało użyte LINQ to XML. Przedstawione trzy metody wystarczą w zupełności do stworzenia własnego dostawcy ustawień. Pozostaje jednak pytanie jak tego dostawcy użyć? Po stworzeniu pliku “settings” należy zaznaczyć daną właściwość i przejść na zakładkę “Properties”. W tej zakładce w polu “Provider” należy wpisać nazwę stworzonego dostawcy. Poniżej został przedstawiony zrzut ekranu, który to obrazuje: W jednym pliku “settings” możemy używać wielu dostawców. W przykładowym projekcie, który stworzyłem, plik “FavoriteMovie.settings” używa mojego dostawcy w ustawieniach “FavoriteMovie” oraz “FavoriteActor”. “FavoriteBand” jest zapisywany przed domyślnego dostawcę LocalFileSettingsProvider. A co z przywróceniem domyślnych ustawień? W tym celu nasz dostawca ustawień musi implementować interfejs IApplicationSettingsProvider. Interfejs ten zawiera trzy metody: GetPreviousVersion, Reset, Upgrade. Ich implementacja została przedstawiona poniżej: public SettingsPropertyValue GetPreviousVersion(SettingsContext context, SettingsProperty property) { return null; } public void Reset(SettingsContext context) { if (File.Exists(this.appSettingsFilePath)) { XDocument document; try { document = XDocument.Load(this.appSettingsFilePath); } catch (Exception exception) { throw new SettingsException("Can't read settings file. Can't reset to default values. See inner exception for details.", exception); } XElement root = document.Element("settings"); if (root == null) this.ThrowExceptionDefaultDataNotFound(); string groupName = context["GroupName"].ToString(); XElement contextSettings = root.Element(groupName); if (contextSettings == null) this.ThrowExceptionDefaultDataNotFound(); foreach (XElement node in contextSettings.Elements()) { XElement defaultValue = node.Element("defaultValue"); if (defaultValue != null) { XElement value = node.Element("value"); if (value == null) { value = new XElement("value"); node.Add(value); } value.Value = defaultValue.Value; } } document.Save(this.appSettingsFilePath); } } public void Upgrade(SettingsContext context, SettingsPropertyCollection properties) { } Metoda GetPreviousVersion oraz Upgrade, nie robią nic ponieważ obecna implementacja mojego dostawcy ustawień, nie uwzględnia wersji programu przy zapisie ustawień do pliku. Uwzględnienie wersji programu nie jest potrzebne jeśli przyjrzycie się implementacji metod SetPropertyValues oraz GetPropertyValues. Z kolei implementacja metody Reset odczytuje wcześniej zapisany plik z ustawieniami i szuka węzłów z domyślnymi ustawieniami. Następnie obecne wartości ustawień są zamienianie na domyślne. Klasy SettingsProvider można użyć np. do stworzenia dostawcy, który sprawdzi się w przypadku aplikacji typu “portable” albo dostawcy, który będzie zapisywać ustawienia w rejestrze. Jeśli potrzebujemy szyfrować ustawienia to stworzenie własnego dostawcy może okazać się dobrym pomysłem. Tutaj znajduje się link do źródeł przykładowego projektu, który korzysta z mojego dostawcy ustawień. Projekt został stworzony w Visual Studio 2010. Klasa dostawcy powinna niedługo trafić do repozytorium DotBeer’a. [Edit 22.08.10 13:03] Po przespanej nocy i ponownym przeczytaniu kodu znalazłem parę błędów w kodzie. Kod we wpisie został zaktualizowany, jak i również przykładowy projekt.

Featured Image
Aug 14, 2010 2 min read 0 comments

[PL] Hello World

Motywacją do powstania tego bloga był start w konkursie Macieja Aniserowicza "Daj się poznać". Będę opisywał tutaj moje zmagania z projektem DotBeer, który powstał w maju 2008 roku. Ostatnia wersja została wydana na początku grudnia zeszłego roku i została pobrana do dzisiaj ponad 3 tysiące razy. Dzięki udziale w konkursie zwiększą się szanse na powstanie nowej wersji, której to wydanie ciągle odwlekałem w czasie. Jeśli ktoś chciałby pogrzebać przy źródłach to są one opublikowane na stronie projektu. A czym w ogóle jest ten projekt? DotBeer jest programem przeznaczonym dla graczy Doty (DotA – Defense of the Ancients). DotA jest modyfikacją do gry Warcraft 3 stworzoną przez firmę Blizzard. W grze dostępnych jest około 100 bohaterów oraz tyle samo przedmiotów do kupienia. W rozgrywce biorą udział dwie drużyny po 5 osób. Celem gry jest zniszczenie bazy przeciwnika. Kluczem do zwycięstwa jest gra drużynowa. W pojedynkę nie zdziałamy zbyt dużo. W ataku na bazę przeciwnika biorą także udział jednostki z naszej bazy. Tych jednostek jednak nie można kontrolować. Gracz ma jedynie kontrolę nad swoim bohaterem. To tak w wielkim skrócie odnośnie samej gry. Wracając do projektu DotBeer. Program zawiera tzw. "buildy" na temat każdego (ok, nie każdego, od ostatniej wersji trochę się zmieniło ;)) bohatera jaki jest dostępny w grze. Taki build zawiera informacje na temat przedmiotów jakie bohater powinien kupić. Dostępna jest również lista z umiejętnościami jakich należy nauczyć naszego bohatera. Poniżej znajduje się zrzut ekranu głównego okna programu dla zobrazowania przedstawionych faktów: Po co w ogóle ten program? DotBeer jest przeznaczony głównie dla nowych graczy oraz dla graczy z wieczną sklerozą (czyli dla mnie ;)). Dzięki temu programowi gracz jest w stanie w bardzo krótkim czasie (alt+tab podczas gry) sprawdzić jakie przedmioty powinien kupić dla danego bohatera. Nowi gracze albo gracze niedoświadczeni w grze danym bohaterem zwykle nie wiedzą jakie przedmioty należy kupić. A przedmioty w grze to sprawa kluczowa. Od nich zależy czy wykorzystamy w pełni potencjał bohatera, którym przyszło nam kierować. O programie DotBeer można myśleć więc w kategorii encyklopedii. Ważniejsze funkcjonalności jakie są obecnie zaimplementowane: wyświetlanie buildów, ich edycja oraz dodawanie nowych buildów, zarządzanie listą (dodawanie, kasowanie, edytowanie) przedmiotów, sprzedawców, bohaterów, tawern, drukowanie buildów, automatyczne aktualizacje programu, kopiowanie informacji z builda do schowka w postaci tekstowej. Zapraszam do zapoznania się z obecną wersją programu DotBeer oraz do zagrania w Dotę 🙂