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!
1. Einleitung
2. Die VideoPage gestalten
3. Die Programmierung der VideoPage
4. Die Hochzeit von Dialog und VideoPage
5. Demoprojekt
6. Ausblick
1. Einleitung
Jetzt ist er endlich da: Der dritte und letzt Teil dieses Tutorials.
Dieser Teil baut auf den ersten und den zweiten Teil des Tutorials auf, also vorher lesen!
Wer beide schon gelesen hat, dem sollte es eigentlich nicht schwerfallen ohne den dritten Teil auszukommen. Hier ist er trotzdem.
2. Die VideoPage gestalten
Eine Art Vorlage für den Dialog ist schon vorhanden. Dieses Bild:
Man hat insgesamt 1 Child-Dialog (die VideoPage!) und 7 Steuerelemente für: Adapter, Output, ..., Anisotropische Filterung.
Ich vergebe auch hier laufende Nummern:
Im zweiten Teil habe ich schonmal eine Vorlage für Pages erstellt und hier die Steuerelemente (=Controls) eingefügt. Ich habe mich für 2 verschiedene Control-Typen entschieden: ComboBox und AutoCheckBox.
Eine ComboBox ist eine "ausklappbare Liste". Darin können beliebig viele Einträge stehen, denn mit der Scrollfunktion ist die Liste schnell durchsucht. Von allem, wovon es beliebig viele gibt (wie Adapter, Auflösungen) wird die ComboBox herhalten müssen.
Bei der Wahl ob Vollbildmodus oder nicht, bzw. VSync an oder nicht, hätte ich auch eine ComboBox benutzen können. Allerdings ist mir das für ein simpler Ja/Nein zu blöd gewesen. Es gibt ja nur Ja oder Nein = An oder Aus. Der "Auto" vor "CheckBox" bewirkt, dass sich Windows um das abhaken kümmert, wenn der Benutzer auf die CheckBox klickt.
Auch wenn hier noch Hand angelegt werden muss: Wie speichert die Eingstellungen?
Ich gehe, wie auch in den anderen Teilen, nicht auf das konkrete Speichern und Laden der Einstellungen ein. Aber eine Struktur, die alle Einstellungen enthält, sollte schon sein. Ich nenne sie VideoSettings
Als nächstes ist die eigentliche VideoPage an der Reihe. Aber wie soll die VideoPage denn überhaupt agieren?
Der Benutzer klickt irgendwo hin, wählt etwas. Bis hierhin macht Windows alles notwendige.
Wenn der Benutzer allerdings das Häkchen einer CheckBox der VideoPage setzt, oder eine andere Auflösung wählt, muss schon etwas unternommen werden. Genau in solchen Momenten wird der geänderte Wert abgefragt und alle davon abhängigen Werte müssen neu aufgelistet werden.
Später klickt der Benutzer dann auf OK bzw. Apply und es wird gespeichert.
Ran an die Sache: Die VideoPage muss von der Klasse SettingsPageBase abgeleitet werden.
Zuerst bekommt die Klasse einige Variablen, darunter die aktuellen Settings, eine IDXGIFactory (von den ListVideo-Funktionen benötigt) und die Handles der Controls:
Dazu kommt noch eine private "Clean"-Methode, die alle Variablen nullsetzt.
Außerdem muss auch reagiert werden können, wenn der Benutzer seine Wahl geändert hat. Darum gibt es für jedes änderbare Control eine "OnChanged"-Methode:
C-/C++-Quelltext
1
2
3
4
5
6
7
8
9
10
11
12
13
/////////////////////////////////////////////////////////Utils für Run//User hat die Wahl geändert.//Diese Funktionen speichern den geänderten Wert in den CurrentSettings.//Die neu verfügbaren (abhängigen) Dinge werden enumeriert.//Die Nachricht, dass etwas geändert wurde, wird versandt.void OnChangedAdapter();
void OnChangedOutput();
void OnChangedFullscreen();
void OnChangedResolution();
void OnChangedVSync();
void OnChangedMultisampling();
void OnChangedAnisotropy();
In diesen Methoden wird erst der gewählte Wert in den "CurrentSettings" gespeichert und dann die davon abhängigen Werte neu aufgelistet. Wenn der Adapter geändert wird, ist es klar, dass neue Auflösungen zur Verfügung stehen können.
Was von wem anhängig ist, zeigt dieses Diagramm:
Die OnCommand-Methode muss also die Aktionen des Benutzers im Auge behalten und gegebenenfalls einer der OnChanged-Methoden aufrufen.
Die AutoCheckBoxes sind nicht das Thema, bei jeder Aktion wechseln sie ihren Zustand. Bei den ComboBoxes muss schonmal nachgefragt werden, ob den überhaupt etwas geändert wurde.
Am Ende entsteht ein typischer Verteiler:
/////////////////////////////////////////////////////////CommandHandling
BOOL VideoPage::OnCommand(WPARAM wParam, LPARAM lParam)
{
switch(LOWORD(wParam))
{
case IDC_VIDEO_ADAPTER:if(HIWORD(wParam) != CBN_SELCHANGE) return(FALSE);
//Adapter wurde geändert
OnChangedAdapter();
return(TRUE);
case IDC_VIDEO_OUTPUT:if(HIWORD(wParam) != CBN_SELCHANGE) return(FALSE);
//Output wurde geändert
OnChangedOutput();
return(TRUE);
case IDC_VIDEO_FULLSCREEN://Fullscreen wurde geändert
OnChangedFullscreen();
return(TRUE);
//Und für die anderen Controls genau sodefault:break;
}
return(FALSE);
}
Kommen wir gleich mal zu OnChangedAdapter
Die Methode fragt den gewählten Adapter ab, speichert ihn zwischen, lässt die Outputs und MultisamplingCounts neu aufzählen, wählt die ersten verfügbaren und ruft OnChangedOutput und OnChangedMultisampling auf.
Klingt nach viel, darum wird das Enumerieren von Outputs und MultisamplingCounts auch exta-Funktionen überlassen: EnumOuputs und Co.
OnChangedAdapter ist demzufolge garnicht mehr so groß:
C-/C++-Quelltext
1
2
3
4
5
6
7
8
9
10
11
12
//User hat die Wahl geändert.void VideoPage::OnChangedAdapter()
{
//Auswahl holen
CurrentSettings.dwAdapter= ComboBox_GetCurSel(hAdapter);
//Der neue Adapter lässt andere Outputs und MSAA zu
EnumOutputs();
OnChangedOutput();
EnumMultisampling();
OnChangedMultisampling();
}
Die anderen OnChanged-Methoden funktionieren nach genau dem selben Prinzip.
Schauen wir und die Methode EnumOutputs exemplarisch nochmal unter die Lupe:
Zuerst werden alle bis dato bekannten Outputs in der Liste rausradiert. Nun wird mit der Funktion ListOutputs aus dem ersten Teil erstmal eine neue Liste mit verfügbaren Outputs gefüllt.
Anschließend werden alle Einträge zur ComboBox der Outputs hinzugefügt.
Danach wird versucht die Auswahl "von damals" wieder herzustellen. Das hat den Grund, dass man so einfach das Laden der Einstellungen erleichtert.
Man müsste also in der Init-Methode die Einstellungen in die CurrentSettings laden, den Adapter aus den Einstellungen wählen und OnChangedAdapter auswählen.
Klingt einfach? Ist es auch, denn alles weitere übernehmen rekursiv die anderen OnChanged-Methoden.
Wenn der Adapter also gändert wird, ruft OnChangedAdapter OnChangedOutput und diese Methode wiederum OnChangedResolutions auf.
Interessant ist auch nochmal die EnumResolutions-Methode und OnChangedResolution, denn hier muss geschickt Breite und Höhe lesbar ausgegeben werden.
Dazu werden sogenannte StringStreams verwendet. Sie sind std::cin und std::cout in einem - überaus praktisch.
Lange Rede, kurzer Code:
void VideoPage::EnumResolutions()
{
//Liste leeren
ComboBox_ResetContent(hResolution);
//Auflösungen auflisten
std::list<Resolution> Resolutions;
ListResolutions(pFactory, CurrentSettings.dwAdapter, CurrentSettings.dwOutput, &Resolutions);
DWORD dwIndex=0;
for(std::list<Resolution>::iterator Resolution= Resolutions.begin(); Resolution != Resolutions.end(); Resolution++)
{
//Eintragen
StringStream Entry;
Entry<<Resolution->dwX<<" x "<<Resolution->dwY;
ComboBox_InsertString(hResolution, dwIndex++, Entry.str().c_str());
}
//Alte Auswahl wiederherstellen
StringStream Choice;
Choice<<CurrentSettings.dwBBSizeX<<" x "<<CurrentSettings.dwBBSizeY;
if(ComboBox_SelectString(hResolution, -1, Choice.str().c_str()) == CB_ERR)
ComboBox_SetCurSel(hResolution, 0);
}
//...void VideoPage::OnChangedResolution()
{
//Auswahl holen und interpretierenchar Buffer[256];
ComboBox_GetLBText(hResolution, ComboBox_GetCurSel(hResolution), Buffer);
StringStream Interpreter;
char cX;
Interpreter.str(Buffer);
Interpreter>>CurrentSettings.dwBBSizeX>>cX>>CurrentSettings.dwBBSizeY;
}
[cpp]
Die anderen Methoden sind ähnlich aufgebaut, wenn nicht sogar durch Copy'n'Paste entstanden.
Aber "back to the roots": Wie startet denn eigentlich der Dialog? Na mit der Init-Methode.
Hier wird nicht so viel Wind gemacht:- Erstmal die SettingsPageBase initialisieren
- Für die ListVideo-Funktionen wird eine IDXGIFactory benötigt, also wird kurzerhand eine erstellt.
- Die Control-Handles müssen noch "initialisiert" werden
- Die Einstellungen, die der Benutzer das letzte mal getroffen hat, müssen geladen werden
- Nach den Einstellungen muss alles weitere enumeriert/selektiert/gecheckt werden.
[cpp]
void VideoPage::Init(HWND hDialog)
{
SettingsPageBase::Init(hDialog);
//Factory erstellen
TRY_DX(CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)(&pFactory)));
//Einstellungen laden//---//Das muss hier nachgeholt werden!//--- //Controls holen
hAdapter = GetDlgItem(GetPageWindow(), IDC_VIDEO_ADAPTER);
hOutput = GetDlgItem(GetPageWindow(), IDC_VIDEO_OUTPUT);
hFullscreen = GetDlgItem(GetPageWindow(), IDC_VIDEO_FULLSCREEN);
hResolution = GetDlgItem(GetPageWindow(), IDC_VIDEO_RESOLUTION);
hVSync = GetDlgItem(GetPageWindow(), IDC_VIDEO_VSYNC);
hMultisampling = GetDlgItem(GetPageWindow(), IDC_VIDEO_MULTISAMPLING);
hAnisotropy = GetDlgItem(GetPageWindow(), IDC_VIDEO_ANISOTROPY);
//Adapter enumerieren, OnChanched senden
EnumAdapters();
OnChangedAdapter(); //Dadurch wird automatisch Output, Resolution und MSAA enumeriert und selektiert//Übrig bleiben noch: Fullscreen, VSync und Anisotropy
Button_SetCheck(hFullscreen, CurrentSettings.bFullscreen);
Button_SetCheck(hVSync, CurrentSettings.bVSync);
EnumAnisotropy();
}
Die Exit-Methode gibt die IDXGIFactory frei und ruft die Exit-Methode der SettingsPageBase auf.
Das wars!
4. Die Hochzeit von Dialog und VideoPage
Um die VideoPage auch anständig nutzen zu können, muss sie nur in die PageList für den Dialog aufgenommen werden.
Das war das Tutorial, wie man einen Direct3D11-SettingsDialog erstellt. Dazu kommt noch ein Tabbed-Dialog Framework, mit dem einfach individuelle Pages drin sind.
Mittlerweile habe ich auch schon eine Page für ein frei konfigurierbares Steuerungssystem. Das war auch nur: Page designen, Page proggen, fertig.
Ich hoffe, dass wenigstens ein oder zwei Leute etwas aus lernen konnten.
Wenn es noch offene Fragen gibt, bitte melden.
Wer zu den wenigen gehört, die sich das Tutorial zu Gemüte geführt haben, für den sollte es kein Problem sein, dieses System in das eigene Spiel einzubauen.
Und wenn das eigene Spiel OpenGL benutzt, dann wird halt eine OpenGL-Page erstellt!
Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »BlazeX« (25.11.2010, 16:01)
Die GUI? WinAPI, das ist für solche (kleinen) Fälle besser geeignet, als erst große Geschütze aufzufahren.
Mit den RC-Dateien ist das etwas blöd, man kann sie nicht in der IDE (ich nehm VC++ 10 Express) erstellen. Darum erstelle ich im Projektordner einfach eine "Neue Txt-Datei" und benenne sie in "Resource.rc" um. Dann bloß noch in der IDE die "existierende Datei" hinzufügen, fertig.
Der Resource Compiler der IDE funktioniert aber tadellos.