Wywołanie metody na obiekcie podanym w stringu (Builder C++)

0

Witam
Robię w builderze prostą grę kółko i krzyżyk, koduję właśnie zdarzenie onclick na jednym z pól.
Nie chcę dla każdego pola tępo pisać ifów sprawdzających turę i ustawiających odpowiednią grafikę, więc po uruchomieniu zdarzenia OnClick na jakimś polu uruchamia się funkcja klik(int nr) (chodzi o numer pola)

Teraz pierwsza sprawa: Jak sprawić by cyferka oznaczająca numer pola (która jest zawarta w nazwie np. pole1) stała się atrybutem tej funkcji?
Próbowałem już klik(Name[5]) i jednak zawsze wychodzi "1" ponieważ wyciąga tą nazwę z Form1. Jednak po 20 minutach prób stwierdziłem że to nie aż takie priorytetowe i stwierdziłem że po prostu powpisuję te numerki ręcznie czyli do każdego onlicka klik(1),klik(2)...

I teraz druga ważniejsza dla mnie sprawa:
Do funkcji klik wchodzi mi int nr, zamieniam go na string, kleję ze stringiem zawierającym "pole" i np. powstaje mi string c="pole1".
Dla uproszczenia, w kodzie nie umieściłem ifów etc bo to nie ma tu znaczenia, chcę po prostu ustawić grafikę obiektu podanego w stringu c na X.
Form1->(nazwa obiektu)->Picture->LoadFromFile("img/x.bmp");
Jak wstawić do tej linii nazwę obiektu która jest w string c?

Próbowałem już wpisać po prostu c, wpisać "pole"+nr, użyć w tej linii uprzednio ustawionego wskaźnika (w lini ustawiającej wskaźnik ten sam problem) lecz bez efektów...

void __fastcall TForm1::pole1Click(TObject *Sender)
{
klik(1);
}

void klik(int nr)
{
String a="pole";
String b=nr;
String c=a+b;
                            //tutaj będą różne ify etc
 Form1->c->Picture->LoadFromFile("img/x.bmp");
}
```c++
1

Możesz:
a) zrobić tablicę wskaźników/referencji dla tych 9ciu komponentów, i poprzez tą tablicę ich używać (indeks 0..8)
b) użyć std::map<Sitring,to_co_chcesz*> to będziesz miał przejście za stringa na obiekt
c) w każdym obiekcie VCL jest pole Tag, właśnie po to, aby tam umieścić jakieś powiązania (integer lub rzutowanie wskaźnika)

0

A- I wtedy miałbym do omawianej linii kodu wkleić tą tablicę? (np wsk[nr]->Pictures...)
Nie wiem czy by to zadziałało skoro pojedyncza zmienna nie działała. Poza tym mam lekkie problemy ze stworzeniem tej tablicy wskaźników, próbuję testowo grafikę ustawić:
TImage* * wsk[3]={&pole1,&pole2,&pole3}; wsk[1]->Picture->LoadFromFile("img/x.bmp");
i dostaję błąd E2288

C- Chyba wyszłoby na to samo, musiałbym zrobić switcha z tym Tagiem i w każdym z nich osobno wersja dla każdego pola, a nie o to mi chodzi (Być może źle coś interpretuję)

B- std::map<Sitring,to_co_chcesz*>
Nie wiem jak tego użyć, ani co wpisać w "tym co chcę" (pole1? c?)
spróbowałem wpisać std::map<String,c*>->Picture->LoadFromFile("img/x.bmp");
(próbowałem także bez "std::" oraz dodając using namespace std;)
lecz dostaję błędy:
E2316 'map' is not a member of 'std'
E2108 Improper use of typedef 'String'
E2188 Expression syntax
(wszystkie z tej samej linii kodu)
Jestem początkującym, więc wielu dla was prostych rzeczy mogę nie wiedzieć, proszę o wyrozumiałość ; )

1

@blaCkCaer dlaczego w punkcie A masz napisane:

TImage* * wsk[3]={&pole1,&pole2,&pole3}

Masz tu podwójny wskaźnik. Użyj pojedynczych i będzie ok. Pamiętaj, że obiekty w VCL są tworzone dynamicznie i formatka zawiera w swojej klasie wskaźniki na utworzone komponenty. Wystarczy więc jak napiszesz:

TImage*  wsk[3]={pole1,pole2,pole3}

Dodatkowo możesz wtedy użyć właściwości Tag. Przykładowy kod będzie wyglądał mniej więcej tak (zakładając na formatce położone 4 komponenty TImage nazwane Image1, Image2, Image3 oraz Image4). Oczywiście metoda OnImageClick powinna znajdować się w sekcji __published klasy okna i mieć odpowiednią definicję.

__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
  Image1->Tag = 0;
  Image2->Tag = 1;
  Image3->Tag = 2;
  Image4->Tag = 3;

  Image1->OnClick = OnImageClick;
  Image2->OnClick = OnImageClick;
  Image3->OnClick = OnImageClick;
  Image4->OnClick = OnImageClick;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::OnImageClick(TObject *Sender)
{
  TImage* ImageTab[4] = {Image1,Image2,Image3,Image4};

  int i = ((TImage*)Sender)->Tag;
  TImage* CurrentImage = ImageTab[i];

  ShowMessage(CurrentImage->Name);
}
//---------------------------------------------------------------------------

Oczywiście już jako pracę domową polecam usunięcie brzydkiego rzutowania w stylu C oraz tablic w stylu C na rzecz np. mapy int -> Timage*

0

@Mr.YaHooo: Wielkie dzięki, podstawiłem sobie resztę pól, ify i wszystko działa.
W punkcie A użyłem tego wskaźnika bo wcześniej wyrzucało mi błąd
E2034 Cannot convert 'TImage * *' to 'TImage *' (szukałem na necie jak to naprawić i właśnie coś takiego tam było, część błędów zniknęła chociaż średnio mam pojęcie co właściwie wtedy zrobiłem)
Jednak jak sobie porównałem z Twoim kodem to się okazało że niepotrzebnie ampersandy powstawiałem i teraz wszystko już działa normalnie.
Z ciekawości spytam, bo nie rozumiem jak działa w Twoim kodzie linia:
int i = ((TImage*)Sender)->Tag;
A dokładniej "((TImage*)Sender)", wiem jakie ma znaczenie w całej funkcji jednak niezbyt pojmuję skąd to pobiera informacje które pole zostało kliknięte.

1
blaCkCaer napisał(a):

E2034 Cannot convert 'TImage * *' to 'TImage *' (szukałem na necie jak to naprawić i właśnie coś takiego tam było, część błędów zniknęła chociaż średnio mam pojęcie co właściwie wtedy zrobiłem)

Zapewne ktoś kto odpowiedział nie do końca wiedział co i jak. Jednak faktycznie na pierwszy rzut oka takie błędy kompilacji można stosować (bez głębszego zastanowienia) dokładając gwiazdki, ampersandy ;)

blaCkCaer napisał(a):

Z ciekawości spytam, bo nie rozumiem jak działa w Twoim kodzie linia:
int i = ((TImage*)Sender)->Tag;
A dokładniej "((TImage*)Sender)", wiem jakie ma znaczenie w całej funkcji jednak niezbyt pojmuję skąd to pobiera informacje które pole zostało kliknięte.

Spójrzmy na to co dokumentacja mówi. Dla ułatwienia przytoczę fragment dokumentacji TImage http://docwiki.embarcadero.com/Libraries/Sydney/en/Vcl.ExtCtrls.TImage oraz zdarzenie http://docwiki.embarcadero.com/Libraries/Sydney/en/Vcl.Controls.TControl.OnClick

Jest to zdarzenie które jest wywoływane podczas kliknięcia każdego komponentu jaki masz na formatce (o ile posiada ono zdarzenie OnClick). Każdy komponent ma swoje zdarzenia. Co więcej kilka komponentów może mieć podłączone tą samą metodę pod zdarzenie OnClick (tak jak pokazałem w przykładzie) Samo zdarzenie jest typu TNotifyEvent http://docwiki.embarcadero.com/Libraries/Sydney/en/System.Classes.TNotifyEvent

Widzisz w dokumentacji, że jako argument jest przekazany TObject* Sender i przechodząc do sedna zacytuję dokumentację:

The Sender parameter is the object whose event handler is called. For example, with the OnClick event of a button, the Sender parameter is the button component that is clicked.

Zatem parametr Sender wskazuje na komponent który wywołał owe zdarzenie. Jako, że zdarzenie ma być uniwersalne to wybrano jako typ parametru wskaźnik na TObject po którym dziedziczą kontrolki. Zatem można bezpiecznie rzutować wskaźnik do kontrolki na wskaźnik na typ TObject Sam TObject nie posiada takiej właściwości jak Tag jednak my pisząc program wiemy, że możemy rzutować w dół w hierarchii obiektów z TObject* na TImage* robimy to by móc dostać się do właściwości Tag A wiemy że takie rzutowanie jest prawidłowe, ponieważ sami podpięliśmy zdarzenie pod kontrolkę TImage i Sender wskazuje na poprawny obiekt Timage.

Jednak ten mój kod który Ci przedstawiłem jest nieco zamotany, chciałem połączyć tablicę wskaźników na komponenty TImage wraz ze wspólnym zdarzeniem OnClick. Taki obrazowy przykład który pokaże, że Sender zawiera wskaźnik na konkretny obiekt.

__fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
{
  Image1->Tag = 0;
  Image2->Tag = 1;
  Image3->Tag = 2;
  Image4->Tag = 3;

  Image1->OnClick = OnImageClick;
  Image2->OnClick = OnImageClick;
  Image3->OnClick = OnImageClick;
  Image4->OnClick = OnImageClick;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::OnImageClick(TObject *Sender)
{
  if (Sender == Image1)
  {
    ShowMessage("Image 1 clicked!");
  }
  if (Sender == Image2)
  {
    ShowMessage("Image 2 clicked!");
  }
  if (Sender == Image3)
  {
    ShowMessage("Image 3 clicked!");
  }
  if (Sender == Image4)
  {
    ShowMessage("Image 4 clicked!");
  }

  TImage* currentImage = (TImage*)Sender;
  ShowMessage(currentImage->Tag);
}
//---------------------------------------------------------------------------

Zatem nie wiem czy nie lepiej tego zrobić w ten sposób niż kombinować z tablicami. Przecież o ile dobrze myślę chcesz po prostu w zdarzeniu załadować odpowiednią bitmapę X albo O. Więc wystarczy tylko rzutować Sender na TImage

void __fastcall TForm1::OnImageClick(TObject *Sender)
{
  TImage* currentImage = (TImage*)Sender;
  currentImage->Picture->LoadFromFile("img/x.bmp");
}
0

Dziękuję za poświęcony czas, rozumiem już wszystko :)

Zatem nie wiem czy nie lepiej tego zrobić w ten sposób niż kombinować z tablicami. Przecież o ile dobrze myślę chcesz po prostu w zdarzeniu załadować odpowiednią bitmapę X albo O. Więc wystarczy tylko rzutować Sender na TImage

Kończąc program zrobiłem właśnie coś podobnego, bo stwierdziłem że skoro ((TImage*)Sender)->Tag; działa tak jak bym się odniósł do konkretnego obrazka to czemu by tego nie wykorzystać dalej

void __fastcall TForm1::OnImageClick(TObject *Sender)
{

  if(tura=='x')
  {
        ((TImage*)Sender)->Picture->LoadFromFile("img/x.bmp");
         p[((TImage*)Sender)->Tag]='x';
  }
   else
  {
        ((TImage*)Sender)->Picture->LoadFromFile("img/o.bmp");
         p[((TImage*)Sender)->Tag]='o';
  }
  ((TImage*)Sender)->Enabled=false;
  sprawdz();                       //sprawdzenie czy ktoś wygrał
  ntura();                         //następna tura
}

Natomiast tablicę z tymi elementami wykorzystałem przy wygranej i resecie aby każde pole włączyć lub wyłączyć bez zbędnego pisania

1

@blaCkCaer i całkiem słusznie zrobiłeś. Jednak na przyszłość taka rada, nie warto używać w nowym kodzie tablic w stylu C. Po prostu dają możliwość generowania błędów (i to takich które będą czasami bardzo trudne do wykrycia). Bardzo łatwo wyjść i zacząć pisać poza obszarem tablicy. Jeśli będą tam siedziały (a w większym programie będą na pewno) jakieś użyteczne inne zmienne, to wtedy je przez przypadek nadpiszesz i program będzie działać niepoprawnie.

Tak samo poczytaj o rzutowaniach. C++ oferuje cały zakres rzutowań i tak samo jak tablice, rzutowanie w stylu C nie jest dobrą praktyką.

Kolejną rzeczą jaką mi się rzuciło w oczy jest używanie magic values. Masz tu na moje oko 3 takie zmienne if(tura=='x') osobiście zrobiłbym tutaj po prostu enuma. Pisząc w ten sposób bardzo prosto o pomyłkę, ponieważ wystarczy nieopatrznie wcisnąć shift na klawiaturze i mieć if(tura=='X') co spowoduje błędne działanie programu. Jak użyjesz typu wyliczeniowego kompilator od razu zgłosi taki błąd, no chyba, że pomylisz wartości. Dalej nazwy plików *.bmp można by też dać jako zmienne (bardzo dobrze jak to będzie zmienna const coby przypadkiem nie zmienić ich wartości) oraz przenieść do jakiejś klasy.

Ostatnią radą jest to abyś pisał po angielsku. Jest to dobra praktyka, ponieważ w większości przypadków taki mix polsko-angielski wydaje się być dziwny. Nie mniej jest to moim zdaniem mniej sztywna reguła niż te które napisałem wcześniej.

3

Radziłbym:

  • trzymanie danych int Board[DefinedSize][DefinedSize]
  • rysowanie na jednym obrazku TBitmap bmp, za pomocą TImages w który ładujesz z jednego pliku w którym narysowane 3 obrazki "_XO"
  • w OnMouseDown do określenia pozycji użyć Board[Y/DefinedSize][X/DefinedSize]

Poniżej cały kod co prawda w delphi ale w builderze prawie top samo:

unit GameMain;

interface uses
  Windows,
  Messages,
  SysUtils,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  ImgList,
  ExtCtrls;

const BoardSize=5;

type
  TAGameMain = class(TForm)
    BoardPic: TImage;
    Images: TImageList;
    procedure BoardPicMouseDown(Sender:TObject;Button:TMouseButton;Shift:TShiftState;X,Y:Integer);
  private
    Board:array[0..BoardSize-1,0..BoardSize-1]of Byte;
    Bmp:TBitmap;
  public
    constructor Create(AOwner:TComponent);override;
    destructor Destroy;override;
  end;

var AGameMain: TAGameMain;

implementation

{$R *.DFM}

constructor TAGameMain.Create(AOwner:TComponent);
begin
  inherited Create(AOwner);
  Bmp:=TBitmap.Create;
  Bmp.Width:=Images.Width*BoardSize;
  Bmp.Height:=Images.Height*BoardSize;
  BoardPic.Picture.Assign(Bmp);
end;

destructor TAGameMain.Destroy;
begin
  Bmp.Free;
  inherited Destroy;
end;

procedure TAGameMain.BoardPicMouseDown(Sender:TObject;Button:TMouseButton;Shift:TShiftState;X,Y:Integer);
var F:Byte;
begin
  X:=X div Images.Width;
  Y:=Y div Images.Height;
  if (X<BoardSize)and(Y<BoardSize) then
  begin
    F:=Board[Y,X];
    F:=(F+1)mod(Images.Count); // To zamienić na logikę gry
    Board[Y,X]:=F;
    Images.Draw(Bmp.Canvas,X*Images.Width,Y*Images.Height,F);
    BoardPic.Picture.Assign(Bmp);
    BoardPic.Invalidate;
  end;
end;

end.

W Images załadowano obrazek:
screenshot-20200723141844.png

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