Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Sheddex

unregistriert

1

03.11.2007, 17:25

Die Windows Konsole

Die Windows Konsole

Allgemeines
Speziell Anfänger in der Programmierung wünschen es sich, die öde Textkonsole wenigstens irgendwie farbig zu machen, da es schon schlimm genug ist, nur mit Text arbeiten zu können.
In diesem Tutorial wird eine Klasse entwickelt, mit der eben diese Einfärbung von Text aber auch vieles andere Möglich ist. So kann zum Beispiel der Titel des Konsolenfensters geändert und die Konsole kann in den Vollbildmodus versetzt werden. Außerdem ist es möglich, Text an einer bestimmten Position im Fenster zu zeichnen und sogar flimmerfrei zu überzeichnen, da nur die Zeichen, die geändert werden müssen, ersetzt werden.

Die Klasse
Hier steht nun alle Deklarationen. Die Erklärungen und Definitionen folgen dann Schritt für Schritt. Hier kann man erst einmal an den Methodennamen ablesen, was die Klasse alles zu bieten hat.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <windows.h>
#include <tchar.h>

enum ConsoleColor
{
  CC_Blue      = FOREGROUND_BLUE,
  CC_Green     = FOREGROUND_GREEN,
  CC_Red       = FOREGROUND_RED,
  CC_Intensity = FOREGROUND_INTENSITY
};

class Console
{
  public:

    bool Init();
    bool SetTitle(const TCHAR* Title);
    bool GetTitle(TCHAR* Title, DWORD Size) const;
    bool SetFullScreenState(bool FullScreen);
    bool GetFullScreenState() const;

    bool  SetCursorPosition(SHORT X, SHORT Y);
    COORD GetCursorPosition() const;
    bool  SetCursorSize(DWORD Size);
    DWORD GetCursorSize() const;

    bool Write(SHORT X, SHORT Y, const TCHAR* String, WORD Color = CC_Blue | CC_Green | CC_Red | CC_Intensity);
    bool Write(SHORT X, SHORT Y, const TCHAR* String, const WORD* Color);
    bool Color(SHORT X, SHORT Y, WORD Color, DWORD Length);
    bool Color(SHORT X, SHORT Y, const WORD* Color, DWORD Length);
    bool ClearScreen();

    static Console& Instance()
    {
      static Console Instance;
      return Instance;
    }

  private:

    HANDLE Handle;
    bool   FullScreenActivated;

    Console();
};


Was auffällt, ist wohl die Inkludierung von tchar.h und die Verwendung von TCHAR. Dies soll die Kompatibilität zu sowohl Ansi als auch Unicode gewährleisten. Verwendet man Ansi, wird TCHAR zu char, verwendet man Unicode, wird TCHAR zu wchar_t.
Die Klasse ist ein Singleton, das heißt dass nur eine einzige Instanz von ihr existiert. Diese kann man mit der letzten Methode bekommen.

Der Konstruktor
Im Kontruktor wird lediglich das Handle der Konsole auf NULL, sowie die Variable, die anzeigt ob der Vollbildmodus aktiviert ist, auf false gesetzt.

C-/C++-Quelltext

1
2
3
Console::Console(void) : Handle(NULL), FullScreenActivated(false)
{
}


Init
Diese Methode muss vor allen anderen aufgerufen werden, da hier das Handle des Konsolenfensters beschafft wird, welches in fast jeder anderen Methode benötigt wird.

C-/C++-Quelltext

1
2
3
4
5
6
bool Console::Init()
{
  Handle = GetStdHandle(STD_OUTPUT_HANDLE);

  return (Handle && Handle != INVALID_HANDLE_VALUE);
}


Zuerst wird hier also das Handle mit der Funktion GetStdHandle ermittelt und gleich danach auf Gültigkeit überprüft. Ist es weder NULL noch hat es den Wert INVALID_HANDLE_VALUE ist es gültig und es wird true zurückgegeben.

SetTitle
Diese Funktion setzt den Titel des Konsolenfensters und erwartet als einzigen Parameter eben jenen.

C-/C++-Quelltext

1
2
3
4
bool Console::SetTitle(const TCHAR* Title)
{
  return (SetConsoleTitle(Title) == TRUE);
}


Hier wird lediglich die Funktion SetConsoleTitle aufgerufen, welche dann den Titel des Konsolenfensters setzt.

GetTitle
Hier folgt nun das Gegenstück zu SetTitle: GetTitle. Diese Methode liefert den Titel des Konsolenfensters und erwartet als ersten Parameter einen Zeiger auf den Speicherbereich, in den der Titel geschrieben wird und als zweiten Parameter die maximale Zeichenanzahl, die dieser fassen kann.

C-/C++-Quelltext

1
2
3
4
bool Console::GetTitle(TCHAR* Title, DWORD Size) const
{
  return (GetConsoleTitle(Title, Size) == TRUE);
}


SetFullScreenState
Die Konsole kann mit der Tastenkombination Alt+Enter in den Vollbildmodus und wieder zurück versetzt werden. Der Druck dieser Tasten wird in dieser Methode simuliert.
Der Einzige Parameter ist eine Variable vom Typ bool, mit der man festlegt, ob der Vollbildmodus aktiviert oder deaktivert werden soll.
Nun gibt es leider ein kleines Problem: Windows Vista unterstüzt den Vollbildmodus der Konsole nicht mehr. Versucht man es doch, kommt eine MessageBox, die eben jenes verkündet. Deshalb wird zu Beginn der Methode abgefragt, ob es sich bei dem Betriebssystem um Vista handelt. Falls ja, wird die Methode abgebrochen und false zurückgegeben.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool Console::SetFullScreenState(bool FullScreen)
{
  OSVERSIONINFO Info;

  Info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

  if (!GetVersionEx(&Info))
    return false;

  if (Info.dwMajorVersion == 6 && Info.dwMinorVersion == 0 && Info.dwPlatformId == VER_PLATFORM_WIN32_NT)
    return false;

  if (FullScreen == FullScreenActivated)
    return false;

  keybd_event(VK_MENU , 0x38 , 0 , 0);
  keybd_event(VK_RETURN , 0x1C , 0 , 0);
  keybd_event(VK_MENU , 0x38 , KEYEVENTF_KEYUP , 0);
  keybd_event(VK_RETURN , 0x1C , KEYEVENTF_KEYUP , 0);
  FullScreenActivated = FullScreen;

  return true;
}


Zu Beginn also wie erwähnt die Abfrage des Betriebssystems. Handelt es sich nicht um Vista, geht es weiter mit dem Vergleich der Variablen FullScreen und FullScreenActivated. Haben diese den gleichen wert, wird false zurückgegeben. Denn wenn der Vollbildmodus aktiviert werden soll (FullScreen ist also true), die Konsole aber bereits im Vollbildmodus ist (FullScreenActivated ist also ebenfalls true), würde eine Simulation der Eingabe den Vollbildmodus deaktivieren, was ja nicht geplant ist. Umgekehrt gilt natürlich das Gleiche.rde eine Simulation der Eingabe sie wieder in den Normalen Modus versetzen, was ja mit der Übergabe von true nicht geplant ist. In diesem Fall wird false zurückgegeben. Selbiges gilt natürlich auch umgekehrt.
Nach diesem Vergleich findet die eigentliche Simulation der Eingabe sowie das Setzen der Membervariable FullScreenActivated auf den Wert von FullScreen.

GetFullScreenState
Mit dieser Methode kann überprüft werden, ob der Vollbildmodus aktiviert ist. Dazu wird einfach FullScreenActivated zurückgegeben.

C-/C++-Quelltext

1
2
3
4
bool Console::GetFullScreenState(void) const
{
  return FullScreenActivated;
}


SetCursorPosition
Auch diese Methode ist sehr simpel aufgebaut: Es werden als Parameter die X- und die Y-Position der gewünschten Cursorposition erwartet. Diese Werte werden dann in eine Struktur des Typs COORD eingesetzt und an SetConsoleCursorPosition übergeben.

C-/C++-Quelltext

1
2
3
4
5
6
bool Console::SetCursorPosition(SHORT X, SHORT Y)
{
  COORD Position = {X, Y};

  return (SetConsoleCursorPosition(Handle, Position) == TRUE);
}


GetCursorPosition
Diese Methode ermittelt die aktuelle Position des Cursors mithilfe der Funktion GetConsoleScreenBufferInfo und liefert diese zurück. Schlägt die Funktion fehl, wird eine COORD-Struktur zurückgegeben, deren X- und Y-Wert jeweils -1 ist.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
COORD Console::GetCursorPosition() const
{
  CONSOLE_SCREEN_BUFFER_INFO Info;
  COORD                      Error = {-1, -1};

  if (!GetConsoleScreenBufferInfo(Handle, &Info))
    return Error;

  return Info.dwCursorPosition;
}


SetCursorSize
Mit dieser Methode kann man die Größe des Cursors festlegen. Übergibt man als gewünschte Größe einen Wert der kleiner als 1 oder größer als 100 ist, wird der Cursor unsichtbar.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bool Console::SetCursorSize(DWORD Size)
{
  CONSOLE_CURSOR_INFO Info;

  if (Size < 1 || Size > 100)
  {
    Info.dwSize   = 1;
    Info.bVisible = FALSE;
  }

  else
  {
    Info.dwSize   = Size;
    Info.bVisible = TRUE;
  }

  return (SetConsoleCursorInfo(Handle, &Info) == TRUE);
}


Die Struktur CONSOLE_CURSOR_INFO beinhaltet die Größe und die Sichtbarkeit des Cursors. Diese Methode soll allerdings so wie oben beschrieben Funktionieren: Ist der Cursor zu klein oder zu groß, wird er unsichtbar gemacht. So kann man sich eine weitere Methode sparen, die für die Cursorsichtbarkeit zuständig wäre.
Die erste Abfrage prüft nun also die als Parameter übergebene Größe. Ist diese kleiner als 1 oder größer als 100 wird die Größe des Cursors in der Informationsstruktur auf 1 gesetzt und die Sichtbarkeit auf FALSE. Ist die Größe um richtigen Wertebereit, wird die Größe in der Informationsstruktur auf die per Parameter übergebene Größe gesetzt und die Sichtbarkeit auf TRUE.
Am Ende der Methode wird die Struktur schließlich an SetConsoleCursorInfo übergeben, um die Änderungen wirksam zu machen. Die Funktion würde übrigens fehlschagen, wenn die Cursorgröße kleiner als 1 oder größer als 100 wäre, was hier ja aber garnicht passiert kann.

GetCursorSize
Diese Methode füllt eine CONSOLE_CURSOR_INFO Struktur mithilfe der Funktion GetConsoleCursorInfo. Schlägt die Funktion fehl, wird als Größe 0 zurückgegeben. Selbiges gilt, wenn der Cursor unsichtbar, das heißt Info.bVisible FALSE ist. Ansonsten wird als Cursorgröße Info.dwSize zurückgegeben.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
DWORD Console::GetCursorSize() const
{
  CONSOLE_CURSOR_INFO Info;

  if (!GetConsoleCursorInfo(Handle, &Info))
    return 0;

  if (!Info.bVisible)
    return 0;

  return Info.dwSize;
}


Write
So, nun folgt eine vergleichsweise große Funktion, da diese auch nicht nur eine einfache Kapselung ist. Diese Funktion erlaubt es, an einer beliebigen Stelle Text zu zeichnen, sogar mit Zeilenumbrüchen, falls erwünscht. Das beste ist allerdings, dass wenn man mit dieser Funktion ein weiteres Mal, allerdings einen anderes Text, an die gleiche Stelle schreibt, wirklich nur die Zeichen, die neu sind ersetzt werden.
Beispiel: Man schreibt denn Text "Hallo". Nun schreibt man den Text "Hello". Statt "Hallo" komplett zu löschen und "Hello" an diese Stelle zu schreiben, wird nur das "a" mit einem "e" überschrieben. Vor allem wenn man plant, ein Spiel mit einer "echten" Welt in der Konsole zu schreiben, ist dies sehr nützlich. Würde man immer alles neu zeichnen würde es sehr stark flimmern und das Programm wäre sehr langsam.
Die ersten beiden Parameter stellen die Position dar, an welcher geschrieben werden soll. Der dritte Parameter ist der Text, der geschrieben werden soll und der vierte legt die Schriftfarbe des gesamten Textes fest. Man kann hier einen oder mehrere, mit einem logischen Oder verknüpften, Werte von ConsoleColor verwenden.
Was innerhalb der Methode passiert, erkläre ich direkt per Kommentar.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
bool Console::Write(SHORT X, SHORT Y, const TCHAR* String, WORD Color)
{
  DWORD Read          = 0;
  WORD  CurrentBuffer = 0;
  TCHAR TextBuffer[4000];
  WORD  ColorBuffer[4000];
  COORD Position = {0, 0};

  //Zuerst wird überprüft, ob überhaupt ein String zum Schreiben übergeben wurde

  if (!String)
    return false;

  //Hier werden ab der per Parameter angegebenen Position die nächsten 4000 Zeichen

  //gelesen und in die Variable TextBuffer übertragen. Da die Position bereits auf 0|0

  //gesetzt wurde, werden eben die ersten 4000 Zeichen gelesen. Wer sich fragt, warum

  //ausgerechnet die ersten 4000, dem sei gesagt, dass die Konsole im Vollbildmodus

  //genau 80*50 groß ist, das heißt 4000 Zeichen in sie hineinpassen. Falls man weiter

  //in die Tiefe gehen will, also über eine Y-Position von 49 hinaus, kann mit einer

  //Funktion wie dieser hier sowieso nichts oder zumindest kaum etwas anfangen und

  //sollte lieber std::cout aus der C++ Standardbibliothek verwenden.

  //Der letzte Parameter ist ein Zeiger auf eine Variable, in welche die tatsächliche

  //Anzahl der gelesenen Zeichen übertragen wird. Diese Zahl ist hier jetzt allerdings

  //unwichtig. Trotzdem wird eine Adresse übergeben, da die Funktion sonst fehlschlägt.

  if (!ReadConsoleOutputCharacter(Handle, TextBuffer, 4000, Position, &Read))
    return false;

  //Diese Funktion funktioniert genau wie die vorige, mit dem Unterschied, dass

  //hier die Farben und nicht die Zeichen ausgelesen werden.

  if (!ReadConsoleOutputAttribute(Handle, ColorBuffer, 4000, Position, &Read))
    return false;

  //Hier wird nun die Positionsstruktur mit den

  //per Parameter übergebenen Werden gefüllt

  Position.X = X;
  Position.Y = Y;

  //CurrentBuffer ist die Position eines Zeichens oder einer Farbe im TextBuffer

  //bzw. ColorBuffer Array. Sie wird mithilfe der X- und Y-Position berechnet.

  CurrentBuffer = Position.X + (Position.Y * 80);

  //Hier wird nun jedes Zeichen des zu zeichnenden Strings durchgegangen und mit dem

  //jeweiligen Zeichen des Buffers verglichen. Sind die Zeichen nicht identisch, wird

  //das alte Zeichen überschrieben.

  //_tcslen wird bei der Verwendung von Ansi zu strlen und bei der Verwendung von

  //Unicode zu wcslen. Jede dieser Funktionen gibt die Länge einer Zeichenkette zurück.

  for (size_t i = 0; i < _tcslen(String); i++)
  {
    //Falls das aktuelle Zeichen die Escape-Sequenz \n ist, wird ein Zeilenumbruch durchgeführt.

    //Dazu wird zur Variable CurrentBuffer 80 addiert, um genau eine Zeile weiter unten zu landen.

    //Allerdings soll die Finale X-Position ja die, die per Parameter übergeben wurde, sein.

    //Deshalb wird erst einmal die aktuelle X-Position subtrahiert (entspricht X = 0) und dann die

    //per Parameter übergebene addiert. Nun wird noch die aktuelle X-Position auf die

    //Anfangs-X-Position gesetzt und die Y-Position um eins erhöht.

    if (String[i] == TEXT('\n'))
    {
      CurrentBuffer += (80 - Position.X) + X;
      Position.X = X;
      Position.Y++;
    }

    else
    {
      //Falls die X-Position größer als 79 ist,

      //muss ein Zeilenumbruch stattfinden.

      if (Position.X > 79)
      {
        Position.X = X;
        Position.Y++;
      }

      //Hier werden nun Zeichen verglichen, bei Unterscheidung

      //wird an die aktuelle Position neu gezeichnet.

      if (String[i] != TextBuffer[CurrentBuffer])
      {
        if (!FillConsoleOutputCharacter(Handle, String[i], 1, Position, &Read))
          return false;
      }

      //Hier findet genau das gleiche statt wie

      //bei den Zeichen, nur diesmal mit Farben,

      if (Color != ColorBuffer[CurrentBuffer])
      {
        if (!FillConsoleOutputAttribute(Handle, Color, 1, Position, &Read))
          return false;
      }

      //Hier werden Positionen aktualisiert

      Position.X++;
      CurrentBuffer++;
    }       
  }

  return true;
}


Hier sollte man sich Zeit lassen, alles zu verstehen, da es schon ein ordentlicher Klotz Code ist.
Wenn man so weit ist, geht es nun weiter.

Write (Überladung)
Write ist eine überladene Funktion. Die zweite Variante erwartet statt einer einzelnen Farbe, die für den ganzen Text gilt, eine Farbe pro Zeichen. Ansonsten gibt es keinen Unterschied zur Standard-Variante, weshalb hier auch nichts speziell erklärt werden muss.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
bool Console::Write(SHORT X, SHORT Y, const TCHAR* String, const WORD* Color)
{
  DWORD Read          = 0;
  WORD  CurrentBuffer = 0;
  TCHAR TextBuffer[4000];
  WORD  ColorBuffer[4000];
  COORD Position = {0, 0};

  if (!String || !Color)
    return false;

  if (!ReadConsoleOutputCharacter(Handle, TextBuffer, 4000, Position, &Read))
    return false;

  if (!ReadConsoleOutputAttribute(Handle, ColorBuffer, 4000, Position, &Read))
    return false;

  Position.X = X;
  Position.Y = Y;

  CurrentBuffer = X + (Y * 80);

  for (size_t i = 0; i < _tcslen(String); i++)
  {
    if (String[i] == TEXT('\n'))
    {
      CurrentBuffer += (80 - Position.X) + X;
      Position.X = X;
      Position.Y++;
    }

    else
    {
      if (Position.X > 79)
      {
        Position.X = X;
        Position.Y++;
      }

      if (String[i] != TextBuffer[CurrentBuffer])
      {
        if (!FillConsoleOutputCharacter(Handle, String[i], 1, Position, &Read))
          return false;
      }

      if (Color[i] != ColorBuffer[CurrentBuffer])
      {
        if (!FillConsoleOutputAttribute(Handle, Color[i], 1, Position, &Read))
          return false;
      }

      Position.X++;
      CurrentBuffer++;
    }       
  }

  return true;
}


Color
Diese Methode ähnelt stark der Write Funktion, mit dem Unterschied, dass kein Text geschrieben, sondern nur gefärbt wird. Der letzte Parameter ist die Anzahl der Zeichen, die gefärbt werden sollen.
Diese Methode sollte man ohne Probleme verstehen können, wenn man die erste Write Methode verstanden hat.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bool Console::Color(SHORT X, SHORT Y, WORD Color, DWORD Length)
{
  DWORD Read = 0;
  WORD  ColorBuffer[4000];
  COORD Position = {0, 0};

  if (!ReadConsoleOutputAttribute(Handle, ColorBuffer, Length, Position, &Read))
    return false;

  Position.X = X;
  Position.Y = Y;

  for (DWORD i = 0; i < Length; i++)
  {
    if (Color != ColorBuffer[i])
    {
      if (!FillConsoleOutputAttribute(Handle, Color, 1, Position, &Read))
        return false;
    }

    Position.X++;

    if (Position.X > 79)
    {
      Position.X = X;
      Position.Y++;
    }   
  }

  return true;
}


Color (Überladung)
Hier gilt das Selbe wie bei der Überladung von Write: Es wird eine Farbe pro Zeichen erwartet.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
bool Console::Color(SHORT X, SHORT Y, const WORD* Color, DWORD Length)
{
  DWORD Read = 0;
  WORD  ColorBuffer[4000];
  COORD Position = {0, 0};

  if (!Color)
    return false;

  if (!ReadConsoleOutputAttribute(Handle, ColorBuffer, Length, Position, &Read))
    return false;

  Position.X = X;
  Position.Y = Y;

  for (DWORD i = 0; i < Length; i++)
  {
    if (Color[i] != ColorBuffer[i])
    {
      if (!FillConsoleOutputAttribute(Handle, Color[i], 1, Position, &Read))
        return false;
    }

    Position.X++;

    if (Position.X > 79)
    {
      Position.X = X;
      Position.Y++;
    }   
  }

  return true;
}


ClearScreen
Zu guter Letzt darf natürlich die Methode zum reinigen des Bildschirms nicht fehlen. Diese Methode überschreibt einfach den ganzen Bildschirm mit Leerzeichen und setzt anschließend den Cursor an die Position 0|0.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
bool Console::ClearScreen(void)
{
  TCHAR Buffer[4000];

  for (unsigned long i = 0; i < 4000; i++)
    Buffer[i] = TEXT(' ');

  if (!Write(0, 0, Buffer))
    return false;

  return SetCursorPosition(0, 0);
}


Das TEXT-Makro setzt übrigens in diesem Fall bei der Verwendung von Ansi das Zeichen auf ' ' und bei der Verwendung von Unicode auf L' '.

Beispielprogramm
Hier folgt nun ein kleines Beispielprogramm, welches alle Set-, Write- und Color-Methoden demonstriert. Man kann es wie einen Film auflaufen lassen, man muss also keine Eingabe tätigen.
Ich lasse das Programm unkommentiert, da es eigentlich ja nur die besprochenen Methoden aufruft.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
int main()
{
  unsigned short Position = 0;
  unsigned short Colors[64];

  Console& C = Console::Instance();

  for (unsigned int i = 0; i < 64; i++)
    Colors[i] = i % 2 ? CC_Red : CC_Blue;

  if (!C.Init())
    return 0;

  Sleep(4000);
  C.SetTitle(TEXT("Die Windows Konsole"));
  Sleep(2000);
  C.SetFullScreenState(true);
  Sleep(4000);
  C.SetCursorPosition(1, 1);
  Sleep(4000);
  C.SetCursorSize(100);
  Sleep(4000);
  C.Write(2, 2, TEXT("Das ist ein Test-Text\nüber mehrere Zeilen."));
  Sleep(4000);
  C.Write(2, 8, TEXT("Das ist ein mehrfarbiger Test-Text\nüber mehrere Zeilen."), Colors);
  Sleep(4000);
  C.Color(2, 2, CC_Green, 64);
  Sleep(4000);
  C.Color(2, 3, Colors, 64);
  Sleep(4000);
  C.ClearScreen();
  Sleep(2000);

  while (true)
  {
    C.Write(0, 0, TEXT("Flimmerfreies überschreiben"));
    C.Write(0, 2, TEXT("#\n#\n#"));
        
    for (unsigned short i = 0; i < 64; i++)
    {
      if (i == Position)
        C.Write(i + 1, 2, TEXT("#\nO\n#"));

      else
        C.Write(i + 1, 2, TEXT("#\n.\n#"));
    }

    C.Write(64, 2, TEXT("#\n#\n#"));

    Sleep(100);
    Position++;

    if (Position == 63)
    {
      Sleep(4000);
      break;
    }
  }

  return 0;
}


Links
Code
MSDN

David_pb

Community-Fossil

Beiträge: 3 886

Beruf: 3D Graphics Programmer

  • Private Nachricht senden

2

03.11.2007, 17:37

Re: [Tutorial]Die Windows Konsole

Ich hab mir mal den Code durchgesehen und mir ist folgendes aufgefallen:

o Wozu brauchst du einen Destruktor, wenn der ohnehin leer bleibt?
o Wieso verwendest du so viel Zeiger und so wenig Referenzen?
o Wieso hat Handle den Datentyp void*, wo doch GetStdHandle() das Macro HANDLE zurück gibt?
o Wieso löst du HANDLE auf, verwendest aber NULL um den Handle zu initialisieren?
o Wieso löst du andere Definitionen auf?
o Wieso löst du nicht konsequent alles auf?
o Wieso bleibst du nicht konsequent bei Unicode/Ansi, wenn du schon beides unterstützen willst?
o Wozu braucht man eine Klasse für das Ganze?

Zitat von »"DragonFlame"«


C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
bool Console::Init(void)
{
  Handle = GetStdHandle(STD_OUTPUT_HANDLE);

  // Wieso prüfst du auf Handle != 0 ?

  if (!Handle || Handle == INVALID_HANDLE_VALUE)
    return false;

  return true; // Wieso nicht einfach: return ( Handle != INVALID_HANDLE_VALUE ); ?

}



Zitat von »"DragonFlame"«


C-/C++-Quelltext

1
2
3
4
5
6
7
bool Console::SetTitle(const TCHAR* Title)
{
  if (!SetConsoleTitle(Title))
    return false;

  return true; // Wieso nicht einfach: return SetConsoleTitle( Title ); ?

}


Dieses seltsame Rückgabekonstrukt tritt noch öfter auf...
@D13_Dreinig

Sheddex

unregistriert

3

03.11.2007, 17:53

1: Gewohnheit.
2: Ich finde sie eindeutiger, wenn man eine Adresse übergibt, weiß man, dass die zugehörige Variable verändert werden kann.
3, 4, 5, 6: Ich löse Standardtypen immer auf, WinAPI-Exklusives aber nicht. Naja, NULL verwende ich halt... darüber hab' ich mir nie Gedanken gemacht, ich habe es halt so gelernt. Ich sollte wohl zukünftig 0 verwenden...
7: ?
8: Warum nicht? Das Handle wird nunmal von allen Funktionen benötigt, also reicht es doch, wenn man es sich einmal beschafft.
9. Ich prüfe Handle auf 0... Mit dem return hast du natürlich Recht.
10. Da kann ich nichts dafür. BOOL ist nunmal nicht bool, weshalb es eine Warnung gibt, wenn ich direkt return verwende. Ich würde es ja auch anders bevorzugen...

grek40

Alter Hase

Beiträge: 1 491

Wohnort: Dresden

  • Private Nachricht senden

4

03.11.2007, 18:22

@ 10.

C-/C++-Quelltext

1
2
3
4
bool Console::SetTitle(const TCHAR* Title)
{
  return (SetConsoleTitle(Title) == TRUE);
}


@ ClearScreen

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
        CONSOLE_SCREEN_BUFFER_INFO info;
        if ( ! GetConsoleScreenBufferInfo( Handle, &info ) )
            return false;
        COORD coord={0,0};
        DWORD tmp;
        if (! FillConsoleOutputAttribute( Handle, info.wAttributes, info.dwSize.X*info.dwSize.Y, coord, &tmp ) ||
            ! FillConsoleOutputCharacter( Handle, _T(' '), info.dwSize.X*info.dwSize.Y, coord, &tmp ))
            return false;
        return SetCursorPosition(0, 0);


@ SetCursorSize

wenn Size == 0 ist schlägt der ganze Kram fehl soweit ich weiss -> wär es nicht sinnvoller, dann implizit die Sichtbarkeit auf FALSE zu setzen und Size auf 1?

@ David_pb und "Wozu braucht man eine Klasse für das Ganze?"

Find ich schon sinnvoller, und sei es nur, dass beim Programmieren mit Intellisense o.ä. die Funktionen dann schön gebündelt aufgelistet werden. Außerdem bietet das die Möglichkeit, << und >> zu überladen, ohne diese Operatoren kann ich mir ne Konsolenoutputklasse garnich mehr vorstellen^^

Bleibt allerdings die Frage, ob im 2. Teil vom Tut dann ma diese operatoren genutzt werden ;)

Sheddex

unregistriert

5

03.11.2007, 18:41

Mmh, hab' mir == bei solchen Sachen vor einiger Zeit abgewöhnt (weil es auch schöner ist) und jetzt muss ich es wieder verwenden...

Mir ist gerade eine weitere Möglichkeit eingefallen:

C-/C++-Quelltext

1
2
3
4
bool Console::SetTitle(const TCHAR* Title)
{
  return SetConsoleTitle(Title) ? true : false;
}


Ich weiß nicht warum, aber irgendwie gefällt mir das so :)

ClearScreen: Ist meine Lösung so schlimm? Zumindest muss ich nur eine Funktion aufrufen...

CursorSize: Stimmt, die Lösung ist nicht schlecht.

Ist das Tutorial im Allgemeinen akzeptabel und lesbar?

6

03.11.2007, 18:52

Hmm ... also:
* Warum überlädst du den Default-{d-tor}?
* Warum holst du dir das Handle nicht im { c-tor }?

C-/C++-Quelltext

1
2
3
4
bool Console::set_title(const TCHAR* title) const
{
    return ::SetConsoleTitle(title);
}

C-/C++-Quelltext

1
2
3
4
bool Console::get_title(TCHAR* title, unsigned long size) const
{
     return ::GetConsoleTitle(title, size);
}

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool Console::SetFullScreenState(bool FullScreen)
{
    ::OSVERSIONINFO Info;
    Info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

    if (!GetVersionEx(&Info))
        return false;

    if (Info.dwMajorVersion == 6 && Info.dwMinorVersion == 0 && Info.dwPlatformId == VER_PLATFORM_WIN32_NT)
        return false;

    if (FullScreen && FullScreenActivated || !FullScreen && !FullScreenActived) return true;
    
    ::keybd_event(VK_MENU , 0x38 , 0 , 0);
    ::keybd_event(VK_RETURN , 0x1C , 0 , 0);
    ::keybd_event(VK_MENU , 0x38 , KEYEVENTF_KEYUP , 0);
    ::keybd_event(VK_RETURN , 0x1C , KEYEVENTF_KEYUP , 0);
    FullScreenActived = FullScreen;
    return true;
}
oder wie?

C-/C++-Quelltext

1
2
3
4
5
bool Console::set_cursor_position(SHORT x, SHORT y) const
{
    ::COORD Position = {x, y};
    return ::SetConsoleCursorPosition(Handle, Position);
} 

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
bool Console::get_cursor_position(::COORD& Position) const
{
    ::CONSOLE_SCREEN_BUFFER_INFO Info;
    if (!GetConsoleScreenBufferInfo(Handle, &Info))
        return false;

    Position = Info.dwCursorPosition;
    return true;
}

Zitat

Natürlich könnte man die Position auch als Rückgabewert zurückgeben, allerdings verwende ich ihn lieber, um die Funktion auf Erfolg zu prüfen. Das kann natürlich je nach persönlicher Vorliebe angepasst werden.
Ehm dann verwende halt mal Exception und schreib nicht so einen *schönen* Code.

... die restlichen Codes spar ich mir ... bis auf:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
bool Console::SetCursorVisibility(bool Visible) const
{
    ::CONSOLE_CURSOR_INFO Info;

    if (!::GetConsoleCursorInfo(Handle, &Info))
        return false;

    Info.bVisible = !!Visible; /* Visible == TRUE */
    
    return ::SetConsoleCursorInfo(Handle, &Info);
}


C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
bool Console::get_cursor_visibility(bool& Visible) const
{
    ::CONSOLE_CURSOR_INFO Info;

    if (!::GetConsoleCursorInfo(Handle, &Info))
        return false;

    Visible = !!Info.bVisible;
    return true;
}


Insgesamt ist der Code einfach wenig C++-Mäßig. Das Auflösen von Makros/Typedefs usw. deutet nicht unbedingt auf besonders große Fähigkeiten hin ...

Naja ich würde die Klasse vollkommen anders designen/schreiben, aber das soll egal sein. Schön das du dir die Mühe gemacht hast, das alles Aufzuschreiben....
Devil Entertainment :: Your education is our inspiration
Der Spieleprogrammierer :: Community Magazin
Merlin - A Legend awakes :: You are a dedicated C++ (DirectX) programmer and you have ability to work in a team? Contact us!
Siedler II.5 RttR :: The old settlers-style is comming back!

Also known as (D)Evil

Sheddex

unregistriert

7

03.11.2007, 21:06

Ich habe nachgedacht... und bin zu dem Schluss gekommen, dass ich nichts auflöse, wenn ich mit der WinAPI arbeite. Microsoft hat sich sicher was dabei gedacht... möglicherweise wird in einer zukünftigen Windows Version DWORD zu unsigned int (Nein, wird es nicht, ist nur ein Beispiel. Wenn, dann wird es zu etwas anderem.) und dann habe ich ein Problem, da ich alles anpassen muss, da ich die Typedefs aufgelöst habe.

Wenn ich allerdings eine Variable nicht für eine WinAPI Funktion brauche, werde ich nach wie vor mit z.B. unsigned long statt DWORD arbeiten...

Jedenfalls ist das Tutorial geupdatet und SetCursorSize und SetCursorVisibility zusammengefasst (kann mir jemand sagen, warum der Cursor im Vollbildmodus einfach nicht mehr unsichtbar werden will, im Fenstermodus aber schon?...).

David_pb

Community-Fossil

Beiträge: 3 886

Beruf: 3D Graphics Programmer

  • Private Nachricht senden

8

03.11.2007, 22:59

DragonFlame: @7: z.B. : if (String == L'\n')

Zitat


Ich habe nachgedacht... und bin zu dem Schluss gekommen, dass ich nichts auflöse, wenn ich mit der WinAPI arbeite. Microsoft hat sich sicher was dabei gedacht... möglicherweise wird in einer zukünftigen Windows Version DWORD zu unsigned int (Nein, wird es nicht, ist nur ein Beispiel. Wenn, dann wird es zu etwas anderem.) und dann habe ich ein Problem, da ich alles anpassen muss, da ich die Typedefs aufgelöst habe.


Wieso hast du HANDLE dann z.B. aufgelöst?

Zitat


C-/C++-Quelltext

1
return SetConsoleTitle(Title) ? true : false; 


Das wirkt genauso unprofessionell und albern wie die andere Lösung.

Nox: @@ David_pb und "Wozu braucht man eine Klasse für das Ganze?"

Die Klasse scheint nicht für mehrere Konsolen designed zu sein, hier hätte es auch ein Namespace oder ein Singleton getan.
@D13_Dreinig

Sheddex

unregistriert

9

04.11.2007, 00:41

1. Sry, hab' ich übersehen.
2. Also du das geschrieben hast, war HANDLE definitiv HANDLE und nicht void*.
3. Ok, ich habe jetzt die Lösung von grek40 genommen.
4. Es war auch ein Singleton, für's Tutorial hab' ich das rausgenommen... ich wollte einfach vermeiden, dass wieder irgendjemand sagt "Singletons sind böse.". Jetzt die Klasse wieder ein Singleton.

Das Gurke

Community-Fossil

Beiträge: 1 996

Wohnort: Pinneberg

Beruf: Schüler

  • Private Nachricht senden

10

04.11.2007, 00:52

Da unter Windows jeder Prozess nur eine Konsole haben kann, ist das doch ein prima Einsatzgebiet für das Singleton Entwurfsmuster ;)

Werbeanzeige