perlon

Użytkownik forum
  • Postów

    425
  • Dołączył

  • Ostatnia wizyta

  • Wygrane w rankingu

    33

Treść opublikowana przez perlon

  1. Operacje na solidach typu Extrude/Move etc. Po wskazaniu krawędzi są zaznaczane dwa sąsiednie face'y. Żeby odjąć jednego face'a mogę użyć opcji Remove ale w poprzednich wersjach można było odjąć przez selekcję z shiftem innej krawędzi. W 2019 shift nie działa przy "deselekcji". Nie wiem czy coś mam nie ustawione czy ten typ tak ma, a wtedy trzeba by to zgłosić. VERNUM = "2018.10.23(38586)_x64"
  2. W uzupełnieniu : zamiast (long)version powinienem użyć Convert.ToInt16(version) i będzie int
  3. Wyszło samo, bo do odczytania wersji bazy użyłem takiego kodu : this.db_con.Open(); SQLiteCommand db_cmd = db_con.CreateCommand(); db_cmd.CommandText = "SELECT MAX([Numer wersji]) FROM wersja_bazy"; var version = db_cmd.ExecuteScalar(); this.db_con.Close(); niestety (int)version; rzuca wyjątek : "Określone rzutowanie jest nieprawidłowe." W tym konkretnym przypadku db_cmd.ExecuteScalar() zwraca object{long}, który niestety nie daje się zrzutować na int. Uzasadnienie: Swego czasu trochę kodowałem hobbystycznie w Clipper'ze między innymi na bazach MySQL. Został mi (może durny) nawyk który przeniosłem na C# że do CREATE, INSERT, DELETE używam w C# metody ExecuteNonQuery (zwraca ilość przetworzonych rekordów) a do SELECT używam ExecuteScalar lub ExecuteReader (zwraca wybrane rekordy) w zależności czy mam pobrać jeden czy więcej rekordów. Uzasadnienie może słabe, ale takie mi tylko przychodzi do głowy. Prawdę mówiąc dla mnie bardziej naturalną technologią w języku obiektowym byłby dostęp do bazy za pośrednictwem ORM'a, ale nie chciałbym tego tematu tu rozwijać. Twój kod pokazuje, że można to zrobić inaczej i myślę że nie ma o co kruszyć kopii.
  4. W zasadzie AddBaseIfNotExist() może być typu void i nic nie zwracać. Jeżeli coś pójdzie nie tak będzie wyjątek jeżeli będzie wszystko ok to następny krok czyli sprawdzenie wersji bazy.
  5. Można wywalić obsługę wyjątków z klasy Base() pozostawiając rzucanie wyjątków. Być może trzeba "zagęścić" rzucanie wyjątków. Być może trzeba stworzyć specjalny typ dziedziczący po Exception(). public class Base { // zmienna path nie jest potrzebna w zadnej z metod // można ją spokojnie przenieść do konstruktora jako zmienną prywatną private string path; // do połączenia wystarczy db_con ustawiony raz w konstruktorze private SQLiteConnection db_con; public Base(string _path) { // w zasadzie path nie jest potrzebny poza konstruktorem // żeby połaczyć sie ponownie z bazą w poszczególnyhc metodach wystarczy // zapamietać obiekt SQLiteConnection db_con this.path = _path + "\\Bazy"; if (!Directory.Exists(this.path)) { Directory.CreateDirectory(this.path); if (!Directory.Exists(this.path)) { throw new Exception("Błąd zakładania katalogu z bazą"); } } string sciezkaBaza = this.path + "\\projekty.sqlite"; this.db_con = new SQLiteConnection($"Data Source = {sciezkaBaza}; Version = 3"); } public bool AddBaseIfNotExist() { bool ok = true; this.db_con.Open(); SQLiteCommand db_cmd = db_con.CreateCommand(); db_cmd.CommandType = CommandType.Text; db_cmd.CommandText = "CREATE TABLE IF NOT EXISTS projekt_lista ([Numer projektu] INT, [Nazwa projektu] TEXT, [Data dodania] TEXT, [Data zakończenia] TEXT, [Ścieżka] TEXT)"; db_cmd.ExecuteNonQuery(); db_cmd.CommandText = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'wersja_bazy'"; var result = db_cmd.ExecuteScalar(); if (result == DBNull.Value) { db_cmd.CommandText = "CREATE TABLE wersja_bazy ([Numer wersji] INT)"; db_cmd.ExecuteNonQuery(); db_cmd.CommandText = "INSERT INTO wersja_bazy VALUES (1)"; db_cmd.ExecuteNonQuery(); } this.db_con.Close(); return ok; } public long GetBaseVersion() { this.db_con.Open(); SQLiteCommand db_cmd = db_con.CreateCommand(); db_cmd.CommandText = "SELECT MAX([Numer wersji]) FROM wersja_bazy"; var version = db_cmd.ExecuteScalar(); this.db_con.Close(); if (version == DBNull.Value) { throw new Exception("Tabela wersja_bazy powinna zawierać co najmniej jeden rekord. Coś poszło nie tak przy zakładaniu tej tabeli"); } else { return (long)version; } } } W każdym bądź razie co by się nie stało, obsługa wyjątku może być tu i będzie tylko jedno miejsce "wyskoku" z programu. try { var baza = new Base(path); if (baza.AddBaseIfNotExist()) { if (baza.GetBaseVersion() != 1) { // tutaj dałbym konwerter na starej bazy na nową, ale narazie nie mamy takiego konwertera więc program tego nie zrobi throw new Exception("Nieprawidłowa wersja bazy. Konwersja niemożliwa w tej wersji programu.Nastąpi zamknięcie programu"); } } } catch (Exception ex) { MessageBox.Show(ex.Message, "Projekty-info"); this.Close(); }
  6. Hmmm... Nie bardzo kumam. Zamiast catch { MessageBox.Show("Nie utworzono bazy. Prawdopodobnie brak możliwości zapisu w katalogu z programem lub brak katalogu Bazy. Program zostanie zamknięty", "Projekty-info"); ok = false;  } return ok; zaproponowałem catch (Exception ex) { MessageBox.Show(ex.Message, "Projekty-info"); ok = false; } return ok; Powyższe wyświetla faktyczną przyczynę skoku do bloku catch. Powrót do kodu : bool results = baza.AddBaseIfNotExist(); if (results != true) { Window.GetWindow(this).Close(); } w zasadzie kończy sprawę. Kod if (!Directory.Exists(this.path)) {  Directory.CreateDirectory(this.path); if (!Directory.Exists(this.path))  { throw new Exception("Błąd zakładania katalogu z bazą"); } } string sciezkaBaza = this.path + "\\projekty.sqlite"; this.db_con = new SQLiteConnection($"Data Source = {sciezkaBaza}; Version = 3"); } rzuca wyjątek w konstruktorze bez jego obsługi. Nie jest zamknięty w żadnym try catch więc powinien wywalić aplikację zanim dojdzie do metody AddBaseIfNotExist
  7. Faktycznie nie przemyślałem tego do końca. Teraz powinno być lepiej. public bool AddBaseIfNotExist() { bool ok; try { this.db_con.Open(); SQLiteCommand db_cmd = db_con.CreateCommand(); db_cmd.CommandType = CommandType.Text; db_cmd.CommandText = "CREATE TABLE IF NOT EXISTS projekt_lista ([Numer projektu] INT, [Nazwa projektu] TEXT, [Data dodania] TEXT, [Data zakończenia] TEXT, [Ścieżka] TEXT)"; db_cmd.ExecuteNonQuery(); db_cmd.CommandText = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'wersja_bazy'"; var result = db_cmd.ExecuteScalar(); if(result == DBNull.Value) { db_cmd.CommandText = "CREATE TABLE wersja_bazy ([Numer wersji] INT)"; db_cmd.ExecuteNonQuery(); db_cmd.CommandText = "INSERT INTO wersja_bazy VALUES (1)"; db_cmd.ExecuteNonQuery(); } this.db_con.Close(); ok = true; } catch( Exception ex ) { MessageBox.Show(ex.Message, "Projekty-info"); ok = false; } return ok; }
  8. A może taka wersja klasy Base() : public class Base { // zmienna path nie jest potrzebna w zadnej z metod // można ją spokojnie przenieść do konstruktora jako zmienną prywatną // ale może się przyda więc zostaje private string path; // do połączenia wystarczy db_con ustawiony raz w konstruktorze private SQLiteConnection db_con; public Base(string _path) { // w zasadzie path nie jest potrzebny poza konstruktorem // żeby połaczyć sie ponownie z bazą w poszczególnyhc metodach wystarczy // zapamietać obiekt SQLiteConnection db_con this.path = _path + "\\Bazy"; if (!Directory.Exists(this.path)) { Directory.CreateDirectory(this.path); if (!Directory.Exists(this.path)) { throw new Exception("Błąd zakładania katalogu z bazą"); } } string sciezkaBaza = this.path + "\\projekty.sqlite"; this.db_con = new SQLiteConnection($"Data Source = {sciezkaBaza}; Version = 3"); } public bool AddBaseIfNotExist() { bool ok; try { this.db_con.Open(); SQLiteCommand db_cmd = db_con.CreateCommand(); db_cmd.CommandType = CommandType.Text; db_cmd.CommandText = "CREATE TABLE IF NOT EXISTS projekt_lista ([Numer projektu] INT, [Nazwa projektu] TEXT, [Data dodania] TEXT, [Data zakończenia] TEXT, [Ścieżka] TEXT)"; db_cmd.ExecuteNonQuery(); db_cmd.CommandText = "CREATE TABLE IF NOT EXISTS wersja_bazy ([Numer wersji] INT)"; db_cmd.ExecuteNonQuery(); db_cmd.CommandText = "INSERT INTO wersja_bazy VALUES (1)"; db_cmd.ExecuteNonQuery(); this.db_con.Close(); ok = true; } catch { MessageBox.Show("Nie utworzono bazy. Prawdopodobnie brak możliwości zapisu w katalogu z programem lub brak katalogu Bazy. Program zostanie zamknięty", "Projekty-info"); ok = false; } return ok; } public long GetBaseVersion() { long result = 0; this.db_con.Open(); SQLiteCommand db_cmd = db_con.CreateCommand(); db_cmd.CommandText = "SELECT MAX([Numer wersji]) FROM wersja_bazy"; var version = db_cmd.ExecuteScalar(); this.db_con.Close(); if (version == DBNull.Value) { throw new Exception("Tabela wersja_bazy powinna zawierać co najmniej jeden rekord. Coś poszło nie tak przy zakładaniu tej tabeli"); } else { result = (long)version; } return result; } }
  9. Żeby mieć zmienną path dostępną wewnątrz klasy dla metod trzeba mieć instancję klasy i przekazać wartość przez konstruktor klasy: public class Base { string path; public Base(string _path) { this.path = _path; } public bool AddBaseIfNotExist() { bool ok; try { string sciezkaBaza = this.path + "\\Bazy"; // } catch { // ... } return ok; } } Żeby zadziałało trzeba powoływać obiekt klasy Base() a metoda już bez argumentu bo klasa ma pole path. var base = new Base(path); var result = base.AddBaseIfNotExist(); Jeżeli pole statyczne w klasie Base() to można tak : public static string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
  10. Jeszcze raz wróciłem do pierwszego posta i po chwili refleksji chciałbym Parikona przeprosić. Źle odczytałem intencję. Jako gospodarz tego wątku pokazuje swoją drogę do wyznaczonego w zamyśle efektu końcowego zaznaczając że powstaje tu kod "as is". Być może odniosłem mylne wrażenie, że moglibyśmy wspólnie jako użytkownicy forum zainteresowani programowaniem wypracować rozwiązania, które będą swego rodzaju efektem wspólnego uczenia się. Niemniej jednak z zainteresowaniem będę obserwował rozwój tego narzędzia. Miałbym również propozycję (nie wiem czy możliwą technicznie) aby w pierwszym poście umieścić link do zasobu zawierającego aktualny stan kompletnego kodu - najlepiej całej solucji. Będzie to pomocne przy indywidualnym testowaniu programu. Być może wystąpią przypadki użycia nie uwzględnione przez autora. Zgłoszenia takie zapewne będą pomocne w rozwoju nakładki.
  11. Mi takie coś działa: var dbPath = "D:\\dev\\Visual Studio 2017 Community"; var dbName = "projekty.sqlite3"; Directory.CreateDirectory($"{dbPath}"); connection = new SQLiteConnection($"Data Source = {dbPath}\\{dbName}; Version = 3"); Może wstaw u siebie Directory.CreateDirectory($"{sciezkaBaza}");
  12. Wg mnie może być OK. Proponuję wprowadzić stosowny interfejs zmieniając nieco nazwę metody skoro ma coś zwracać: public interface IBase { bool AddBaseIfNotExist(); int GetBaseVersion(); } public class Base : IBase { ... } W klasie Base wprowadzić i zaimplementować ten interface implementując metody instancyjne a nie statyczne. Taka abstrakcja pozwoli w przyszłości podmienić klasę Base() jakąś inną klasą realizującą takie same zadania na innym silniku bazy danych bez zmiany kodu klasy MainWindow. Jeżeli klasa MainWindow będzie operować na interfejsie a nie klasie konkretnej nie trzeba będzie jej zmieniać ale będzie ją można rozszerzać o nową funkcjonalność. https://pl.wikipedia.org/wiki/Zasada_otwarte-zamknięte
  13. Brakuje jeszcze dopisania rekordu do tabeli "wersja_bazy", bo skoro właśnie została stworzona to na dzień dobry powinien w niej się już znaleźć jeden rekord z numerem wersji bazy.
  14. Wersjonowanie bazy jest dobrym pomysłem. Warto w momencie jej tworzenia zapisać w niej informację, którą wersją programu została stworzona. Dobrymi kandydatami do zapisu są poniżej zaznaczone pola. Jedno z nich, być może wszystkie. Dostępność tych danych z poziomu kodu: var ass = Assembly.GetExecutingAssembly(); var attr = ass.GetCustomAttributes(false); var assemblyVersion = ass.GetName().Version.ToString(); var guid = ((GuidAttribute)attr.Single(a => a.GetType().Name == "GuidAttribute")).Value; var fileVersion1 = ((AssemblyFileVersionAttribute)attr.Single(a => a.GetType().Name == "AssemblyFileVersionAttribute")).Version; var fileVersion2 = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion; Być może są jeszcze inne na to sposoby, które chętnie poznam.
  15. Dla zainteresowanych. W metodzie CheckTable() zamiast : command.CommandText = $"PRAGMA table_info({tableName})"; var queryResult = command.ExecuteReader(); while (queryResult.Read()) { if ( !structure.ContainsKey( queryResult.GetString(1) )) { // dodaj kolumne jezeli brakuje command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {queryResult.GetString(1)} {structure[queryResult.GetString(1)]}"; command.ExecuteNonQuery(); } } connection.Close(); powinno być : command.CommandText = $"PRAGMA table_info({tableName})"; var queryResult = command.ExecuteReader(); while (queryResult.Read()) { structure.Remove(queryResult.GetString(1)); } queryResult.Close(); foreach( var item in structure) { // dodaj kolumne jezeli brakuje command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {item.Key} {item.Value}"; command.ExecuteNonQuery(); } connection.Close(); command.Dispose();
  16. Powyższe jest proste, natomiast obciąża klasę MainWindow odpowiedzialnością za bazę danych, jej lokalizację, za ilość tabel (może być ich przecież więcej niż jedna) jak i konkretną strukturę tabel. Jest więc mniej uniwersalne niż bardziej. Ponadto klasa MainWindow nie powinna się zajmować tworzeniem bazy danych. Jej odpowiedzialnością jest komunikacja z użytkownikiem (wyświetlanie danych, przyjmowanie zdarzeń i przekazywanie ich wgłąb aplikacji). Kontroler istnienia bazy nie musi być w innej bibliotece. Znajduje się w tej samej przestrzeni nazw co klasa MainWindow czyli w tym samym dll ma tylko inną odpowiedzialność. Jeżeli zajdzie potrzeba w aplikacji dołożenia dodatkowej kolumny do tabeli "projekt_lista" np. "typ_projektu" (projekt budowalny, projekt wykonawczy, koncepcja...etc) to co będzie konieczne żeby takie pole dodać i jeszcze w dodatku bez utraty dotychczasowych danych? Czy na pewno powinna się tym zająć klasa MainWindow. Nie żebym się czepiał. Proszę potraktuj to jako głos w dyskusji. Odsyłam też do https://pl.wikipedia.org/wiki/Antywzorzec_projektowy (szukaj : Boski obiekt ) i https://pl.wikipedia.org/wiki/Spaghetti_code
  17. Chciałbym zaproponować pewne rozwiązanie w zakresie tworzenia bazy danych rozdzielające odpowiedzialność klas. Mianowicie tworzeniem i operacjami na bazie danych nie powinna sie zajmować klasa służąca do tworzenia interfejsu użytkownika. Wkładanie logiki obsługi bazy do UI spowoduje mocne rozbudowanie metod obsługi zdarzeń i w rezultacie uzyskamy jedną superklasę wszystko robiącą w swoich metodach. Poniższa propozycja jest jedynie namiastką koncepcji, ale jeżeli zostanie zaakceptowania przez Parikona i umieszczona w kodzie, mogę tą część BackEnd'u dalej uszczegółowić. Oczywiście wszelkie uwagi, propozycje i krytyka mile widziane. Komentarze do fragmentów kodu poniżej każdej sekcji. Na początek proponuję wydzielenie struktury tabel do osobnej klasy: using System.Collections.Generic; namespace ProjektForum { public class DatabaseStructure { public static Dictionary<string,string> GetProjectsTableStructure() { Dictionary<string, string> result = new Dictionary<string, string>(); result.Add("Numer_projektu", "INT"); result.Add("Nazwa_projektu", "TEXT"); result.Add("Data_dodania", "TEXT"); result.Add("Data_zakonczenia", "TEXT"); result.Add("Sciezka", "TEXT"); return result; } } } Mamy tu metodę statyczna zwracającą słownik gdzie kluczem jest nazwa kolumny a wartością jest typ tej kolumny. Rozwiązanie nieidealne ale na początek wystarczy. Potrzebna nam będzie klasa która sprawdzi czy baza istnieje i czy zawiera odpowiednie kolumny. Umówmy się że na tym etapie używamy na sztywno bazy SQLite jak było w zamyśle Parikona aby maksymalnie uprościć obsługę. Nie budujemy przecież systemu uniwersalnego i super elastycznego. Klasa coś na taki kształt : using System; using System.Collections.Generic; using System.Data; using System.Data.SQLite; namespace ProjektForum { public class DatabaseChecker : IDisposable { private bool isDisposed = false; private SQLiteConnection connection; public DatabaseChecker(string _dbName) { connection = new SQLiteConnection($"Data Source = {_dbName}; Version = 3"); } // poniewaz w SQLite brakuje ALTER TABLE ALTER COLUMN poki co metoda jest w stanie jedynie dodac brakujaca kolumne // w przyszlosci nalezy uzupelnic funkcjonalnosc o modyfikacje typu kolumn porzez tymczasowa kopie tabeli // stworzenie nowej i skopiowanie danych ze starej na nowa tabele public void CheckTable( string tableName, Dictionary<string, string> structure) { string parseText; SQLiteCommand command; command = connection.CreateCommand(); command.CommandType = CommandType.Text; connection.Open(); // zagwarantowanie, ze tabela w bazie istnieje parseText = ParseCreateTableCommand(structure); command.CommandText = $"CREATE TABLE IF NOT EXISTS {tableName} ({parseText})"; command.ExecuteNonQuery(); // zagwarantowanie, ze tabela zawiera co najmniej kolumny z listy "structure" command.CommandText = $"PRAGMA table_info({tableName})"; var queryResult = command.ExecuteReader(); while (queryResult.Read()) { if ( !structure.ContainsKey( queryResult.GetString(1) )) { // dodaj kolumne jezeli brakuje command.CommandText = $"ALTER TABLE {tableName} ADD COLUMN {queryResult.GetString(1)} {structure[queryResult.GetString(1)]}"; command.ExecuteNonQuery(); } } connection.Close(); } string ParseCreateTableCommand(Dictionary<string, string> structure) { string result = ""; foreach( var item in structure) { if (result != "") { result += ","; } result += $"[{item.Key}] {item.Value}"; } return result; } public void Dispose() { connection.Dispose(); GC.SuppressFinalize(this); } } } Przyjmuje ona w konstruktorze ścieżkę do bazy danych SQLite. Zawiera jedną publiczną metodę CheckTable której argumentami jest nazwa tabeli oraz słownik z oczekiwaną strukturą tabeli. Metoda prywatna ParseCreateTableCommand zamienia słownik na stringa w formacie wymaganym przez komendę CREATE TABLE. Klasa posiada implementację interfejsu IDisposable do zarządzania swoim istnieniem o czym już za chwilę. Jak tego użyć? Ano prosto. Klasa ma w zasadzie jeden cel. Sprawić aby w podanej bazie danych istniała tabela o podanej nazwie i określonej strukturze. Robimy to tak: using (var dc = new DatabaseChecker(databasePath + "\\" + databaseFileName)) { dc.CheckTable("projekt_lista", DatabaseStructure.GetProjectsTableStructure()); } Wywołanie klasy zamykamy w bloku using żeby nie pozostawiać śmieci w pamięci. Po to był nam potrzebny interfejs IDisposable. Metoda Dispose w klasie DatabaseChecker załatwia zniszczenie obiektu klasy SQLiteConnection. Powyższy kod należy umieścić w metodzie startowej całej aplikacji. Wg mnie zanim jeszcze zaczniemy myśleć o wyświetleniu jakiegokolwiek okna. Dobrym kandydatem na to byłaby metoda Initialize() w klasie implementującej interfejs IExtensionApplication o której była już w tym wątku i nie tylko mowa. Załatwia nam to jako taką integralność bazy danych z aplikacją i kontrolę w momencie ładowania modułu .dll poleceniem NETLOAD. Jeżeli wystąpi potrzeba dołożenia jakiegoś pola do tabeli wystarczy zmienić metodę zwracającą odpowiedni słownik, albo dołożenie nowej tabeli wymaga dołożenia kolejnej metody w klasie DatabaseStructure. Mamy jedno miejsce gdzie siedzi definicja bazy a nie gdzieś rozsiane po kodzie i trudne do znalezienia. Na koniec. Powyższe rozwiązanie jest dalece niedoskonałe i nie załatwia wielu problemów na które natrafimy w bardziej zaawansowanym korzystaniu z bazy SQL. Ponadto można je zrobić bardziej elastycznym wykorzystując wzorce projektowe w tym np. metodę wytwórczą fabryki czy uzyskiwanie instancji obiektów za pomocą wstrzykiwania zależności, ale to jest już zupełnie inna historia, a ja nie chciałbym się wymądrzać bo za mało o tym jeszcze wiem. Niemniej mam chęć się tego poduczyć i mogę pewne rozwiązania wdrożyć jeżeli tylko Parikon zechce je wprowadzić do kodu, który tu tworzymy.
  18. Drobna uwaga. Plik z bazą danych nie powinien mieć rozszerzenia .sql Zwyczajowo .sql jest to typ pliku zawierającego skrypt w języku SQL. Warto zmienić nazwę bazy na np. "projekty.sqlite". DB Browser for SQLite który sobie zainstalowałem przy otwieraniu bazy poszukuje plików z jednym z rozszerzeń : .db .sqlite .sqlite3 lub db3 I jeszcze jedna uwaga. Wydaje mi się że w nazwach kolumn należałoby unikać spacji. Może to w przyszłości powodować komplikacje przy migracjach na inne platformy lub wprowadzać niejednoznaczności przy np. parsowaniu nazw kolumn jeżeli kiedyś taka potrzeba zajdzie. Wpisanie podkreślenia zamiast spacji nic nie kosztuje a daje jednoznaczność nazwy.
  19. Mam w biurze takie "dzieci ikonek". Dla nich do kompletu jeszcze by się przydała operacja odwrotna przywracająca skojarzenie napisu z faktycznym wymiarem (TextOverride = <>)
  20. W 2019 SP1 w dalszym ciągu jest sprzężona ze sobą zmiana Annotation scale z Standard scale. Nie wiem czy to jest zamierzone ale wg mnie obie skale powinno się ustawiać niezależnie. Tak na marginesie. Jest gdzieś porządny opis jak to działa, żeby nie używać metody "na macanta"?
  21. Jeszcze tylko drobna uwaga. W powyższym polecenie (netload "appdebug") można wołać bezpośrednio z pliku netload.scr. Używanie pośrednika autostart.lsp w zasadzie jest nadmiarowe, ale jakoś tak z przyzwyczajenia kod lispowy trzymam w .lsp. Przy innych okazjach w tym pliku wykonuję inne operacje dla meritum sprawy w naszym przypadku nieistotne.
  22. Można nieco przyspieszyć pracę z kodem i umożliwić debugowanie kodu. Szczegóły na filmiku. Z góry przepraszam za słaby warsztat w nagrywaniu bo to mój pierwszy raz 😉
  23. A może by wyłączyć właściwość path do innej klasy np. klasy służącej do zapisu i odczytu konfiguracji całej aplikacji i pobierać tą wartość w dowolnym miejscu i czasie. Ponadto można uzupełnić konfigurację o możliwość wskazania innej lokalizacji pliku bazy danych jeżeli tylko zajdzie taka potrzeba. Można to ustawiać np. w osobnym formularzu Ustawienia. Poniżej klasa typu Singleton która może trzymać różne ustawienia aplikacji. Umieściłem w niej funkcje zapisujące i odczytujące parametry dowolnego okna dialogowego w rejestrze windows. Dla każdego wywołanego okna można w zdarzeniu Loaded umieścić wywołanie var config = new ProjektForumSettings(); config.RestoreSettingsWindow(config.AppName, this); w zdarzeniu Closed var config = new ProjektForumSettings(); config.SaveSettingsWindow(config.AppName, this); Klasa ta przyjmuje domyślne pewne wartości (appName i appPath) ale można te wartości ustawiać np. przy starcie aplikacji. Będą one dostępne w całej aplikacji zawsze takie same. Zakłada również że ona sama znajduje się w tym samym assmebly co reszta aplikacji dla prawidłowego ustawienia appName i appPath. A klasa może wyglądać np. tak: using Microsoft.Win32; using System.Windows; namespace ProjektForum { public sealed class ProjektForumSettings { private static ProjektForumSettings instance = null; private int counter = 0; private string appPath; private string appName; public static ProjektForumSettings Instance { get { if (instance == null) { instance = new ProjektForumSettings(); } return instance; } } private ProjektForumSettings() { counter = 1; appPath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(this.GetType().Assembly.Location)); appName = this.GetType().Assembly.GetName().Name; } public string AppPath { get { return appPath; } } public string AppName { get { return appName; } } public void SetAppPath(string path) { appPath = path; } public void SetAppName(string name) { appName = name; } public void SetKeyToRegister(string appName, string name, object value) { RegistryKey regKey = Registry.CurrentUser.OpenSubKey("Software", true); RegistryKey subKey = regKey.CreateSubKey(appName); subKey.SetValue(name, value); } public object GetKeyFromRegister(string appName, string name, object defaultValue) { RegistryKey regKey = Registry.CurrentUser.OpenSubKey("Software", true); RegistryKey subKey = regKey.CreateSubKey(appName); return subKey.GetValue(name, defaultValue); } public void SaveSettingsWindow(string appName, Window appWindow) { SetKeyToRegister(appName, "WindowState", (int)appWindow.WindowState); SetKeyToRegister(appName, "WindowLeft", appWindow.RestoreBounds.Left); SetKeyToRegister(appName, "WindowTop", appWindow.RestoreBounds.Top); SetKeyToRegister(appName, "WindowWidth", appWindow.RestoreBounds.Width); SetKeyToRegister(appName, "WindowHeight", appWindow.RestoreBounds.Height); } public void RestoreSettingsWindow(string appName, Window appWindow) { appWindow.Left = (int)GetKeyFromRegister(appName, "FormLeft", appWindow.Left); appWindow.Top = (int)GetKeyFromRegister(appName, "FormTop", appWindow.Top); appWindow.Width = (int)GetKeyFromRegister(appName, "FormWidth", appWindow.Width); appWindow.Height = (int)GetKeyFromRegister(appName, "FormHeight", appWindow.Height); appWindow.WindowState = (WindowState)GetKeyFromRegister(appName, "WindowState", appWindow.WindowState); } } }
  24. Tego nawet nie sugerowałem. To był tylko mój przykład na podział strukturalny. UI może być w czymkolwiek. WPF jak najbardziej. Zresztą można to robić równolegle z innymi systemami UI bez szkody dla projektu. Ważne tylko, aby aplikacja była tak napisana, żeby dało się interfejs użytkownika np podmienić na inny bez utraty funkcjonalności samej aplikacji. To oczywiście śpiew przyszłości ale warto pomyśleć o modularności i zastosowaniu wzorca MVC. Dla aplikacji typu desktop to byłby wzorzec MVP (ModelViewPresenter). Część View w tym wzorcu to może być np. Windows Presentation Foundation.