Historia zmian na formie - jak to sprytnie robicie?

Odpowiedz Nowy wątek
2019-03-14 20:06
0

Cześć,
w program w których czasami muszę udowadniać że klient w trakcie używania sam narozrabiał a nie jest to problem systemu stosuję zapisywanie zmian jakie zaszły na formie. Robię to już od dawna i jakoś to działa chociaż kosztuje mnie to dużo pracy.

Wygląd to mnie więcej tak że w bazie tworzę tabelę o nazwie historia a w niej kilka kolumn mniej więcej takich jak:

  • data_utworzenia (typu timestamp),
  • uzytkownik (zalogowany do programu),
  • uzytkownik_system (uzytkownik zalogowany do Windows),
  • komputer (nazwa stanowiska Windows),
  • panel (numer formy która "wpisuje" rekord do bazy),
  • operacje (czyli zapisane otwartym tekstem historie zmian lub ich brak jeśli klient wycofa się ze zmian).

Tak naprawdę wiem wszystko i wiem jakie dane były wcześniej. W ten sposób mogę dokładnie prześledzić co się działo z danymi i kto rozrabiał.

Oczywiście wiem że takie rozwiązanie powoduje bardzo duży przyrost danych w bazie ale robię to z pełną świadomością i nie o to tutaj chodzi.

Gdy tworzony jest rekord taka operacja wygląda mniej więcej tak (przykład z ostatniego programu):

procedure Tnowy_model.dodaj;
var
  licznik: integer;
  historia: string;
begin
  try
    with DM.qrytemp, SQL do
    begin
      Close;
      Clear;
      Add('INSERT INTO modele (id_marka, model, opis, utworzyl)');
      Add('VALUES (:id_marka, :model, :opis, :utworzyl)');
      ParamByName('id_marka').AsInteger := id_marka;
      ParamByName('model').AsString := edtnazwa.Text;
      ParamByName('opis').AsString := mmoopis.Text;
      ParamByName('utworzyl').AsString := main.uzytkownik;
      ExecSQL;
    end;

    with DM.qrytemp, SQL do
    begin
      Close;
      Clear;
      Add('SELECT LAST_INSERT_ID()');
      Open;
      First;
      licznik := DM.qrytemp.Fields[0].AsInteger;
    end;
  except
    ShowMessage('Błąd! Nie dodano modelu. Sprawdź dane!');
  end;

  try
    historia := 'Utworzenie modelu' + #13#10;
    historia := historia + 'Nazwa: ' + edtnazwa.Text + #13#10;
    historia := historia + 'Marka: ' + cbbmarka.Text + #13#10;
    historia := historia + 'Opis: ' + mmoopis.Text + #13#10;

    with DM.qryhistoria, SQL do
    begin
      Close;
      Clear;
      Add('INSERT INTO historia (panel, utworzyl, rekord, operacja, stanowisko) VALUES (:panel, :utworzyl, :rekord, :operacja, :stanowisko)');
      ParamByName('panel').AsInteger := 2;
      ParamByName('utworzyl').AsString := main.uzytkownik;
      ParamByName('rekord').AsInteger := licznik;
      ParamByName('operacja').AsString := historia;
      ParamByName('stanowisko').AsString := main.komputer;
      ExecSQL;
    end;
  except
    ShowMessage('Błąd! Nie zapisano zmian w historii operacji!');
  end;

  with DM.qrymodele, SQL do
  begin
    Clear;
    Close;
    Add('select mo.id, mo.id_marka, mo.model, mo.opis, mo.usun, mo.data_utworzenia, mo.utworzyl, ma.marka from modele mo');
    Add('inner join marki ma on mo.id_marka = ma.id where mo.usun = :usun order by ma.marka, mo.model');
    ParamByName('usun').AsInteger := 0;
    Open;
  end;

  DM.qrymodele.Locate('id', licznik, []);
end;

Nie kosztowało to zbyt wiele pracy ale też i panel zawierał tylko kilka pól edycyjnych.

W przypadku edycji już jest troszkę gorzej. Pokazując panel muszę najpierw uzupełnić dane w kontrolkach:

procedure Tmodele.btnedycjaClick(Sender: TObject);
begin
  edit_model.cbbmarka.Items.Clear;

  with DM.qrytemp, SQL do
  begin
    Close;
    Clear;
    Add('select marka from marki order by marka ASC');
    Open;
    First;
  end;

  while not DM.qrytemp.Eof do
  begin
    edit_model.cbbmarka.Items.Add(DM.qrytemp.FieldByName('marka').AsString);
    DM.qrytemp.Next;
  end;

  edit_model.cbbmarka.ItemIndex := edit_model.cbbmarka.Items.IndexOf(DM.qrymodele.FieldByName('marka').AsString);

  edit_model.id := DM.qrymodele.FieldByName('id').AsInteger;
  edit_model.id_marka := DM.qrymodele.FieldByName('id').AsInteger;
  edit_model.edtnazwa.Text := DM.qrymodele.FieldByName('model').AsString;
  edit_model.mmoopis.Text := DM.qrymodele.FieldByName('opis').AsString;
  edit_model.lbltworca.Caption := DM.qrymodele.FieldByName('utworzyl').AsString;
  edit_model.lbldata_utworzenia.Caption := DateTimeToStr(DM.qrymodele.FieldByName('data_utworzenia').AsDateTime);
  edit_model.Left := modele.Left + 20;
  edit_model.Top := modele.Top + 20;
  edit_model.Show;
end;

Następnie na formie edit_model tworzę zmienne dla historii:

  private
    h_marka, h_nazwa, h_opis: string;

Następnie w zdarzeniu OnShow zapamiętuję aktualny stan kontrolek:

procedure Tedit_model.FormShow(Sender: TObject);
begin
  h_marka := cbbmarka.Text;
  h_nazwa := edtnazwa.Text;
  h_opis := mmoopis.Text;
  edtnazwa.SetFocus;
end;

No i teraz jest już z górki, wystarczy w trakcie update rekordu użyć if-ów i wszystko staje się jasne:

procedure Tedit_model.btnzapiszClick(Sender: TObject);
var
  historia: string;
begin
  try
    with DM.qrytemp, SQL do
    begin
      Close;
      Clear;
      Add('UPDATE modele SET id_marka=:id_marka, model=:model, opis=:opis WHERE id=:id');
      ParamByName('id_marka').AsInteger := id_marka;
      ParamByName('model').AsString := edtnazwa.Text;
      ParamByName('opis').AsString := mmoopis.Text;
      ParamByName('id').AsInteger := id;
      ExecSQL;
    end;
  except
    ShowMessage('Błąd! Nie zaktualizowano modelu. Sprawdź dane!');
  end;

  try
    historia := 'Aktualizacja modelu' + #13#10;
    if cbbmarka.Text <> h_marka then
      historia := historia + 'Marka: było: ' + h_marka + ' jest: ' + cbbmarka.Text + #13#10;
    if edtnazwa.Text <> h_nazwa then
      historia := historia + 'Model: było: ' + h_nazwa + ' jest: ' + edtnazwa.Text + #13#10;
    if mmoopis.Text <> h_opis then
      historia := historia + 'Opis: było: ' + h_opis + ' jest: ' + mmoopis.Text + #13#10;
    if ((edtnazwa.Text = h_nazwa) and (cbbmarka.Text = h_marka) and (mmoopis.Text = h_opis)) then
      historia := historia + 'Nic nie zmieniono!';

    with DM.qryhistoria, SQL do
    begin
      Close;
      Clear;
      Add('INSERT INTO historia (panel, utworzyl, rekord, operacja, stanowisko) VALUES (:panel, :utworzyl, :rekord, :operacja, :stanowisko)');
      ParamByName('panel').AsInteger := 2;
      ParamByName('utworzyl').AsString := main.uzytkownik;
      ParamByName('rekord').AsInteger := id;
      ParamByName('operacja').AsString := historia;
      ParamByName('stanowisko').AsString := main.komputer;
      ExecSQL;
    end;
  except
    ShowMessage('Błąd! Nie zapisano zmian w historii operacji!');
  end;

  with DM.qrymodele, SQL do
  begin
    Clear;
    Close;
    Add('select mo.id, mo.id_marka, mo.model, mo.opis, mo.usun, mo.data_utworzenia, mo.utworzyl, ma.marka from modele mo');
    Add('inner join marki ma on mo.id_marka = ma.id where mo.usun = :usun order by ma.marka, mo.model');
    ParamByName('usun').AsInteger := 0;
    Open;
  end;

  DM.qrymodele.Locate('id', id, []);

  Close;
end;

Jak napisałem - jakoś to działa ale powiem szczerze że wyjątkowo mi się nie podoba. Po pierwsze te ify po drugie wszystko jest ok gdy pól edycyjnych jest nie więcej niż paręnaście (właśnie mam na pulpicie program w którym jest prawie 200 checkbox-ów).

Podzielcie się pomysłem jak to zrobić bardziej zgrabnie, może jakaś uniwersalna klasa która sama "wykryje" wszystkie pola edycyjne i zapamięta ich stan, następnie porówna przed opuszczeniem formy czy coś się zmieniło i zapisze zmiany? W taki powiedzmy sprytniejszy sposób ustawiam stan kontrolek na tej 200 elementowej formie:

  for i := 0 to ComponentCount - 1 do
  begin
    if components[i] is TCheckBox then
      TCheckBox(Components[i]).Checked := False;

    if components[i] is TEdit then
      TEdit(Components[i]).Text := '';
  end;

A może jest jakiś gotowiec do tego a ja go nie znam?

Zapraszam do dyskusji.
Robert

edytowany 1x, ostatnio: furious programming, 2019-03-14 20:08

Pozostało 580 znaków

2019-03-15 17:26
1
robertz68 napisał(a):

Cóż mam powiedzieć. Robię wszystko dokładnie w taki sposób jaki zganiłeś w poście powyżej.

Nie mogę nawet nic napisać na swoją obronę no może poza tym że sam do tego kiedyś doszedłem i wyrobiłem sobie pewne reguły, ale jak widać nie zrobiłem tego zgodnie z zasadami.

Mógłbym poprosić o jakieś sugestie, jak robić to prawidłowo ale ... chyba już sobie odpuszczę - nie mam siły :).

A ja się dopiero rozkręcałem... ;-)

W każdym razie dziękuję za to co napisałeś.

A proszę bardzo. Ale i tak napiszę co nieco od siebie...
Na dzień dzisiejszy masz dwa wyjścia:

  1. Stare (dobre?) podejście Delphi oparte o Datasety. Naprawdę da się pisać apki bazo-danowe szybko i w miarę wygodnie. Wszelkiej maści DataEntry czy CRUDy wręcz legendarnie ekspresowo.
  2. Nowe podejście, oparte o Live Bindings. Wymaga nowego Delphi (gdzie nowe, to powiedzmy nie starsze jak XE7. Co prawda LiveBindings jest od wersji XE2, ale...).

ad. 1
To jest stosunkowo proste do nauczenia i efektywnego używania. Problem polega na dwóch sprawach:
a) Jest bardzo, ale to od groma, materiałów na ten temat. Tylko, że absolutnie zdecydowana większość z nich pokazuje jak zrobić potworki (jakieś komponenty niewizualne na jakiś krzywych DataModule... Zupełnie bez sensu. Ale to taki RAD-way...) tj. szybko i przyjemnie wyklikane rozwiązanie, które działa.
b) Niestety takie rozwiązania nie nadają się dla aplikacji, które działają na skomplikowanym modelu. Inaczej - w aplikacjach napisanych w ten sposób, najczęściej, w ogóle nie można mówić o tym, że model istnieje. Rozwój takiego oprogramowania polega na dodawaniu kolejnych funkcjonalności... Niestety to się prędzej czy później źle kończy.
Poza tym, niespecjalnie wygodnie (doprecyzuje - niespecjalnie wygodnie to jest przerośnięty eufemizm w tym przypadku) się utrzymuje i rozwija nowe wymagania w takiej aplikacji.

Tak czy siak, jeśli to standardowa aplikacja klient-serwer z niespecjalnie fikuśnym modelem danych, to oczywiście bez problemu da radę. poza tym, w stosunku do tego jak ty to robisz - to kosmos wygoda! :)

ad. 2)
Rozwiązanie oparte o LiveBindings, które umożliwiają binding (wiązanie) dwukierunkowe typu obiekt.właściwość <--> obiekt.właściwość. To znaczy (w skrócie), że mając wiązanie pomiędzy kontrolką (np. TEdit) a właściwością obiektu, ten edit automatycznie reaguje na zmiany wartości w obiekcie, a obiekt reaguje na zmianę wartości w kontrolce. Zresztą, identycznie to działa przy kontrolkach dbAware (czyli TDBEdit->DataSource->TDataSet), z tym że wiązanie jest tylko i wyłącznie do TDataSet.
Oczywiście istnieje też możliwość wiązania kontrolek z TDataSet, nie tylko z obiektem.

Tego typu rozwiązanie pokazuje siłę, ale dopiero wtedy, gdy zaprzęgniemy do pracy ORMa (np. TMS Aurelius). Niestety Embercadero jakby "zapominało" tym fakcie i nic podobnego do ORMa nie istnieje w standardzie.

Problem polega na tym, że to zdecydowanie bardziej złożone od zwykłych DataSetów. A zatem krzywa wejścia i uczenia się jest zdecydowanie bardziej stroma.
Ale daje pełne wsparcie dla OOP, szalenie wygodnie się koduje i stosunkowo łatwo napisać skomplikowaną reaktywną logikę.
Różnica pomiędzy tymi podejściami jest taka, że teoretycznie na DataSetach też da się osiągnąć podobne efekty w zakresie przetwarzania skomplikowanego modelu. Ale koszt ich wprowadzenie i utrzymania jest znacznie wyższy, poza tym musimy liczyć się z większą liczbą błędów.

Posłużę się takim wykresem:
Delphidb.png

Proszę traktować to jako umowny przykład, a nie wynik jakiś tam badań. Jak ten wykres będzie wyglądał w danym projekcie, to po prostu zależy od klasy problemu, który projekt ma rozwiązać.
Ale upraszczając, można powiedzieć że nie ma co się pchać w modelowanie OOP dla aplikacji bazodanowych, kiedy wiemy że ta aplikacja jest stosunkowo prosta pod katem modelu.
I odwrotnie, jeśli mamy model, który:

  • jest powodem tworzenia projektu
  • jest sercem systemu i jego największą wartością
  • a modelowanie jest największym wyzwaniem w danym projekcie
    To zdecydowanie warto przysiąść i zrobić to dobrze.

I jeszcze jedno; baza danych nie jest modelem, ponieważ reprezentuje statyczny schemat, który w żadne sposób nie odzwierciedla logiki zachowania i działania danego systemu. Baza danych, de-facto, przechowuje stan obiektów biznesowych.

Pozostało 580 znaków

2019-03-15 17:55
0

@wloochacz czy była by szansa abyś zamieścił proste przykłady jak Ty stosujesz w praktyce wyżej opisane przez Ciebie zagadnienia ?
Chylę czoła za wiedzę jaką posiadasz.

edytowany 1x, ostatnio: PrzemysławWiśniewski, 2019-03-15 17:56

Pozostało 580 znaków

2019-03-15 18:02
0

Ciężko mi cokolwiek teraz napisać.
Oczywiście pisząc że sobie odpuszczę nie byłem szczery i od razu zacząłem szukać wiedzy w necie. Nie jest to jednak takie proste. Opis jakiś sposobów użycia pewnych technologii można znaleźć w internecie ale nie otrzymam tam informacji o tym co jest dla mnie kluczowe i czego być może dowiem się tutaj dzięki dobrym ludziom.
A chodzi o to, jaką technologię wybrać do aplikacji które najczęściej piszę. Są to zazwyczaj proste programy bazodanowe, korzystające z bazy MS SQL lub Firebird, najczęściej jest to kilkanaście tabel, maksymalnie kilkanaście tysięcy rekordów. Jest trochę relacji i to wszystko.
Większość moich programów to coś w rodzaju katalogów, a to części elektronicznych, a to pracowników i ich identyfikatorów drukowanych na drukarkach kart.
Takie proste programiki.

Aktualnie jak już wiadomo jakich rozwiązań używam, chciałbym to zmienić. Nie wiem czy od razu przyjrzeć się bindowaniu bo widzę że mocno zachwalasz to rozwiązanie (unikałem tego jak ognia bo myślałem że to jakaś fanaberia o której za chwilę wszyscy zapomną), czy może jednak na razie pomyśleć o cachedupdate, ponieważ będzie to chyba dla mnie prostsze do moich prostych aplikacji a i widzę pewne ułatwienie w pisaniu programów.

Poradzisz coś?

Pozostało 580 znaków

2019-03-15 18:53
0
robertz68 napisał(a):

Ciężko mi cokolwiek teraz napisać.
Oczywiście pisząc że sobie odpuszczę nie byłem szczery i od razu zacząłem szukać wiedzy w necie. Nie jest to jednak takie proste. Opis jakiś sposobów użycia pewnych technologii można znaleźć w internecie ale nie otrzymam tam informacji o tym co jest dla mnie kluczowe i czego być może dowiem się tutaj dzięki dobrym ludziom.
A chodzi o to, jaką technologię wybrać do aplikacji które najczęściej piszę. Są to zazwyczaj proste programy bazodanowe, korzystające z bazy MS SQL lub Firebird, najczęściej jest to kilkanaście tabel, maksymalnie kilkanaście tysięcy rekordów. Jest trochę relacji i to wszystko.
Większość moich programów to coś w rodzaju katalogów, a to części elektronicznych, a to pracowników i ich identyfikatorów drukowanych na drukarkach kart.
Takie proste programiki.

To są kluczowe informacje i bardzo dobrze, że to napisałeś.
Nie chodzi o to przecież aby kogoś/coś deprecjonować, że robi to czy tamto.
Ale możemy pojechać za to, jak to robi :D

Aktualnie jak już wiadomo jakich rozwiązań używam, chciałbym to zmienić. Nie wiem czy od razu przyjrzeć się bindowaniu bo widzę że mocno zachwalasz to rozwiązanie (unikałem tego jak ognia bo myślałem że to jakaś fanaberia o której za chwilę wszyscy zapomną), czy może jednak na razie pomyśleć o cachedupdate, ponieważ będzie to chyba dla mnie prostsze do moich prostych aplikacji a i widzę pewne ułatwienie w pisaniu programów.

Poradzisz coś?

Z radością! ;-)
W Twoim przypadku, zdecydowanie DataSety.
Wdrożysz się szybko i będziesz Pan zadowolony, ale... to też trochę zależy :D
ChechedUpdates jest bardzo dobre, ale jego jakość zależy tak naprawdę od biblioteki (a więc inaczej to działa w ADO, FireDAC, ZEOS, itd.) której używasz. Po prostu ten element nie jest zaimplementowany na poziomie TDataSet, a każda biblioteka przynosi własną jej implementację.

Oczywiście nie znam wszystkiego, ale to co znam to mogę się wypowiedzieć.

  1. FireDAC (dawniej AnyDAC) - chyba zdecydowanie the best. Ciężko znaleźć obszar (oczywiście mówimy o DataSetach...) w którym FireDAC nie jest co najmniej w czubie. A sama obsługa CachedUdpates jest niedoścignionym wzorem dla innych. Ale to wynika architektury tej biblioteki. Jeśli używasz zamiennie czy równocześnie MSSQL/Firebird, to zdecydowanie polecam.
  2. ADO - największa męczarnia z tym była. Słaba implementacja w standardzie Delphi, można by rzec, że podstawowa. Co do zasady ADO samo z siebie złe nie jest, ale ADO dostarczone w Delphi (jako dbGO) jest po prostu słabe.
  3. Używałem kiedyś jeszcze FIBPlus (tylko dla IB/Firebird). To był krzywo napisany kod, który działał wyśmienicie :) Niestety, chyba już nie żyje.

Miałem jeszcze incydentalne spotkania z IBX (nie polecam) i SDAC (polecam).
Testowałem też kiedyś ZEOS'a, ale to było bardzo dawno temu i nie ma co o tym pisać Wiem tylko, że nowsze wersje są na pewno szybsze, ale jak z jego funkcjonalnością, to po prostu nie wiem.

Łatwiej by też było coś doradzić, gdybyś powiedział jakiego Delphi używasz i czego używasz do baz danych?

Pozostało 580 znaków

2019-03-15 19:13
0
PrzemysławWiśniewski napisał(a):

@wloochacz czy była by szansa abyś zamieścił proste przykłady jak Ty stosujesz w praktyce wyżej opisane przez Ciebie zagadnienia ?

W sumie nie wiem co Ci powiedzieć... Konkret by się przydał, to znaczy - jakbyś opisał swój problem, albo zdefiniował dziedzinę problemu, to łatwiej by było coś pokazać czy wyjaśnić.
Już nie raz coś tam opisywałem i pokazywałem...
Pierwsza część postu jest w miarę "prosta" (do używania), druga była nawet propozycją - ale bez odzewu.
Ale ja naprawdę nie mam czasu, aby pisać elaboraty.
Także bardzo proszę - chcesz o czymś porozmawiać, spoko - ale określ granice ;)
Poza tym, jak piałem wcześniej, bardzo wiele zależy od biblioteki, której używamy.
No chyba, że skupimy się na ogólnych zagadnieniach, ale... mam trudność, bo nie rozumiem co tam jest niejasnego czy na tyle ciekawego, aby to omawiać?
Zatem zadaj konkretne pytanie, a postaram się ustosunkować.

Chylę czoła za wiedzę jaką posiadasz.

Dzięki.

Pozostało 580 znaków

2019-03-15 20:04
0

Przyznam że ja do nauki programowania Delphi nie maiłem dobrych wzorców zostałem wdrożony do utrzymania i dewelopeki istniejącego już od wielu lat systemu informatycznego i tak naprawdę przekwalifikowałem się po blisko 5 latach pracy w automatyce i plc.
System jaki zastałem w obecnej firmie był rozwijany prawdopodobnie od Delphi 4.
Kod pod zdarzeniami przycisków to była normalna rzecz. Za mojej kadencji z BDE przeszliśmy przez ADO do Firedac-a spora ilość kodu została przeniesiona pod metody prywatne/publiczne do sekcji Formatek i DataModule. Niestety nadal nie czuję aby to wyglądało tak jak wyglądać powinno :/
Byłem już kilka razy na seminariach jakie prowadził Bogdan Polak, jedno z nich było w temacie programowania SOLID. Byłem trochę zawiedziony tymi wykładami bo nie zobaczyłem praktycznego zastosowania w kodzie. Tak mi już chyba pozostało że najlepiej łapę na przykładach kodu. Fajnie by było jak byś zamieścił przykład jak powinno się budować aplikację od podstaw, pierwsza formatka – tylko z kodu … czy np. z „dizajnera” np. dwa pola login-hasło przycisk
walidacja wprowadzonych danych czy „zainsertowanie” jakiś danych do bazy i wyświetlenia ich na formatce. Jak to zrobić typowo obiektowo z zasadami SOLID.

Pozostało 580 znaków

2019-03-15 22:37
0

@wloochacz: ależ idealnie się składa bo po kilku latach pracy przy BDE (później chwilę ADO) przeszedłem całkowicie na FireDAC - jak @PrzemysławWiśniewski . Aktualnie mam dostęp do Delphi XE8 Ent (firmowe) i oczywiście 10.3 Community. Jak mogę to używam Community, chyba że chcę połączyć się z MS SQL to muszę przejść na XE8 bo niestety nie ma drivera FireDAC do MS SQL w Community - ale to zapewne wszyscy wiedzą.

Wracając do projektu, myślę że korzystając z twojej pomocy utworzę bardzo prosty projekt do nauki / zabawy, będzie to książka adresowa.
Podstawowe założenia:

  • aplikacja sieciowa w technologii klient-serwer z bazą na serwerze MS SQL,
  • dostęp do bazy bezpośrednio, nie przez rest-a czy soap,
  • oczywiście DataSet-y bo tak się umówiliśmy,
  • główna forma będzie się składała z grida ze wszystkimi rekordami w bazie,
  • pod gridem dwa buttony do pokazania nowej formy na której będzie można utworzyć / edytować dane. Dzięki komponentom bazodanowym chcę zrobić to na jednej formie a nie na dwóch osobnych jak to robiłem zwykle - mam nadzieje że nie jest to niezgodne z zasadami?.

baza danych to dwie tabele. Pierwsza to uzytkownicy w której są dwie kolumny:

  • [id] typu [int] IDENTITY(1,1) NOT NULL,
  • [uzytkownik] typu varchar NULL,

Druga tabela to adresy w której są następujące kolumny:

  • [id] [int] IDENTITY(1,1) NOT NULL,
  • [nazwa] varchar NULL,
  • [ulica] varchar NULL,
  • [kod_poczt] varchar NULL,
  • [miejscowosc] varchar NULL,
  • [id_uzytkownik] [int] NOT NULL,
  • [wspolny] [bit] NOT NULL,

No dobrze, tworzę formę główną, na niej grid.
screenshot-20190315221741.png

Po naciśnięciu na btnNowy pokaże się nowa forma
screenshot-20190315222547.png

Żeby nic nie mieszać już teraz (bo mam przecież pełno moich pomysłów) zapytam jak to zrobić porządnie aby:

  • forma pokazała się z pustymi polami (normalnie pojawią się wartości z aktualnego rekordu),
  • pod combobox-a była podłączona kolumna uzytkownik z tabeli uzytkownicy,
  • w czasie dodawania rekordu do bazy powinna zadziałać auto inkrementacja na kolumnie id,
  • do tabeli adresy wpisujemy id uzytkownika (z tabeli uzytkownicy) wybranego w combobox-ie a nie jego imię i nazwisko

To tak na początek.

Tak na szybko, bo muszę się czymś zająć... Wrzuć to na np. GitHuba/GitLaba i będziemy rozwijać. A dyskutować możemy sobie gdzie bądź ;-) A jeszcze na szybko - wrzuć ten projekt z bazą danych (najlepiej backup pustej bazy mssql w wersji 2014 lub firebird). - wloochacz 2019-03-16 10:45

Pozostało 580 znaków

2019-03-16 13:26
1

uff, wrzuciłem zarys projektu na githuba https://github.com/RobertZawadzki/adresy

Zapraszam do zabawy

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0