C/C++ מדריכי
C++ הקדמה להיררכית מחלקות ב
הצגה מזורזת :C++ הבנת C הקדמה לתכנות ב דף הבית

המושג של הירארכית מחלקות הוא לעיתים קרובות דבר שקשה למתכנתי ++C חדשים לרכוש בו מיומנות. בעוד שקל להגדיר אותו (היראכית מחלקות היא סדרה של מחלקות שנבנות אחת על גבי השניה תוך שימוש בירושה ובפונקציות וירטואליות) וקל להצדיק אותו (הסיבה העיקרית ליצירת הירארכית מחלקות היא לאפשר שימוש חוזר), זה קשה למדי לעבור מרעיון למיומנות כיוון שהירארכיות נוטות להיות מסות גדולות של קוד שמתייחס לעצמו. יכול לקחת די הרבה זמן להשתלט על הירארכיה גדולה, במיוחד אם זו הראשונה שלך. עם זאת, אחת הדרכים הטובות ביותר ללמוד על ++C היא לבחון ולהשתמש בהירארכיות קיימות. בצורה זו ניתן להתחיל להבין את היתרונות של ירושה ופולימורפיזם, וכן להשיג גישה למספר טכניקות תכנון בעלות ערך. הבעיה עם הירארכיות קיימות היא שברוב המקרים הן די מורכבות. לדוגמא, הירארכית המחלקות MFC מכילה מאות מחלקות, אלפי פונקציות חברות ועשרות אלפי שורות קוד. אנשים עשויים לבלות שנים בלהבין לחלוטין את כל האספקטים של ההירארכיה.

המטרה של מאמר זה היא להציג את SST - ה Super Simple Toolkit - כדוגמא להירארכית מחלקות. SST היא הירארכית מחלקות GUI שמאפשרת לך ליצור אפליקציות GUI פשוטות ביותר בקלות רבה. ככזו, ניתן להשוות אותה ל MFC, למחלקות ה OWL של Borland, ל Turbo Vision, להירארכית המחלקות של Java, וכו'. SST, כפי שמרמז שמה, היא פשוטה ביותר. אולם, בפשטותה היא נושאת שלושה יתרונות חשובים:

1. כיוון שהיא כל כך פשוטה, ניתן לשלוט בה תוך יום או יומיים בלבד. כלומר, ניתן להבין לחלוטין את השימוש של ההירארכיה, ליצור אפליקציות פשוטות בעזרתה, להוסיף לה מספר שליטות חדשות, ולהבין את קוד המקור שיוצר אותה, תוך מספר ימים בלבד.

2. SST, למרות שהיא קטנה, נושאת את כל הרעיונות המרכזיים שנמצאים בהירארכיות מחלקות גדולות יותר. באמצעות חשיפתך לתפיסות היסודיות האלה, SST יכולה להקל מאוד את ההבנה של הירארכיות גדולות יותר.

3. SST מאוד ניידת, ועברה הידור תחת AT&T's Front Compiler ,Borland C++ , Visual C++ , וכו'. לכן קל מאוד להסתגל למגוון סביבות לימוד.

SST היא לכן כלי לימוד בעל ערך רב למתכנת ++C המתחיל.

הקדמה להירארכית SST

כל הירארכיות ה GUI מאפשרות לך ליצור עצמי GUI סטנדרטים כגון כפתורים, תוויות, פסי גלילה, וכו'. SST אינה שונה: היא רק קצת יותר פשוטה. ערכת כלי הפיתוח של SST מאפשרת לך ליצור כפתורים, תוויות, ושטחי עריכה. היא פשוטה מספיק כך שניתן להשתמש בה, להבין אותה ולהרחיב אותה תוך מספר מועט של ימים בלבד. בנוסף, ניתן לבחון את קוד המקור (כל קוד המקור לSST מופיע בסוף מאמר זה) וללמוד מטכניקות הירושה שהיא מכילה. SST נוהגת לפי תבניות שימוש סטנדרטיות שניתן לראות ברוב ערכות כלי הפיתוח ה GUI.

SST היא ערכת כלי פיתוח ניידת, מבוססת תווים. תכונה זו מאפשרת לה לרוץ כמעט על כל פלטפורמה, אף על פי שהיא קצת מדכאת יופי. תכנית טיפוסית שנוצרת כתוצאה משימוש ב SST נראית כך:

בחלון זה, יש תוויות סטטיות ("Fahrenheit Temperature" ,"Celsius Temperature", ו "212"), אזור קלט שבו הודפס הערך 100, ושני כפתורים ("Convert", ו "Quit"). אזור הקלט הוא בפוקוס, כפי שמיוצג על ידי סימן ה "=" (סימן השוויון) סביבו. האפליקציה המוצגת מיישמת ממיר פשוט מצלסיוס לפרנהייט.

תוכנית SST יכולה להכיל את אובייקטי ה GUI הבאים:

  • חלון ראשי (אחד)
  • תוויות
  • כפתורים
  • אזורי קלט

SST מבינה את סמנטיקות המקלדת הבאות:

  • שינוי הפוקוס באמצעות מקש ה tab (או Shift-tab)
  • הפעלת כפתורים באמצעות מקש ה return

בקרות עריכה מקבלות הקשות על מקשים ואת תו ה backspace.

הירארכית SST מכילה 10 מחלקות. שש ממחלקות אלה מאורגנות בהירארכיה כמוצג להלן:

הארבע הנותרות הן מחלקות עזר:

Event - מחלקת מאורע בה משתמשים באובייקט: HandleEvent

ObjectList - מחלקת רשימת אובייקטים בה משתמשים בחלון

Screen - מחלקת ציור מסך בה משתמשים באובייקט

Strclass - מחלקת מחרוזת פשוטה שמשמשת לקלט

בסעיפים הבאים תלמד איך ליצור אפליקציות פשוטות ע"י שימוש במחלקות אלה, וכמו כן תלמד איך פועל קוד המקור למחלקות.

יצירת אפליקציית SST הפשוטה ביותר

כל האפליקציות של SST חולקות מספר אלמנטים יסודיים. לא משנה כמה פשוט או מורכב, תמיד תבצע את הצעדים הבאים בכל אפליקציית SST:

  • הפק את האפליקציה שלך ממחלקת החלון. הצהר על הבקרות שלה כחברות
  • צור בנאי למחלקה המופקת שיוצר ומאתחל את הבקרות בתוך החלון
  • רמוס את הפונקציה החברה במחלקת החלון HandleEvent כדי לטפל במאורעות מכפתורים

באמצעות שימוש בצעדים אלה, האפליקציה הפשוטה ביותר שניתן ליצור היא חלון שמכיל כפתור יציאה. כדי ליצור כזו אפליקציה, תתחיל בירושה ממחלקת החלון:

#include <IOSTREAM.H>
#include <STRSTREA.H>

#include "button.h"
#include "rect.h"

#include "window.h"
class App: public Window
{
public:
    App( char *s, Rect &r );
    virtual void HandleEvent( Event &event );
};

App app( "Test", Rect(1, 1, 80, 24) );

...

void main()
{
    app.Execute();
}

שים לב שקוד זה מצהיר על מחלקה בשם App שנגזרת מחלון, ושמחלקה נגזרת זו רומסת את הבנאי ואת הפונקציה HandleEvent. לאחר מכן התכנית יוצרת מופע של מחלקה זו כמשתנה גלובלי ומעבירה לו את המחרוזת "Test" (שתשמש בתור שם החלון) ואת הגודל והמיקום של החלון על המסך (ניתן להניח שהמסך הוא רשת תווים דו-ממדית ברוחב של 80 תווים ובגובה של 24 תווים, כך שהגודל המתואר כאן ממלא את המסך). לאחר מכן הקוד קורא לחבר המחלקה Execute בפונקציה הראשית כדי שהכדור יתחיל להתגלגל.

לאחר מכן תיצור בנאי חדש למחלקה App. תפקידו של בנאי זה להכניס בקרות לחלון.

const int QUIT  = 100;

App::App( char *s, Rect &r ): 
    Window( s, r ), count( 0 )
{
    Insert( new Button(" Quit", Rect(50, 18, 57, 20), QUIT) );
}

פונקצית ה Insert יוצרת מופע חדש של מחלקת הכפתור, כשהיא מעבירה לכפתור את שמו ("Quit"), מיקומו וגודלו, וזיהוי (ID) של 100 בקבוע בשם QUIT. הזיהוי ישמש לטפל במאורעות שנוצרים על ידי הכפתור.

לאחר מכן תרמוס את הפונקציה החברה HandleEvent במחלקת החלון כדי לטפל במאורעות שנוצרים על ידי כפתור היציאה.

void App::HandleEvent( Event &event)
{
    Window::HandleEvent( event );

    if( event.type == COMMAND )
    {
        switch( event.message )
        {
            case QUIT:
              Close();
              screen.Clear();
              break;
        }
    }
    event.type = CLEAR;
}

קוד זה קודם כל קורא לפונקצית ברירת המחדל של HandleEvent במחלקת החלון על מנת שיוכל לטפל בכל מאורע שהוא מבין אוטומטית. לאחר מכן כל המאורעות שלא טופלו נבחנים כדי לראות אם הם מאורעות COMMAND. אם כן, הם נבחנים כדי לראות אם הם הגיעו מכפתור היציאה. אם כן, האפליקציה מסתיימת על ידי סגירת החלון וניקוי המסך.

אם תאסוף את החתיכות האלה ותריץ את האפליקציה, תמצא שהחלון מופיע, שהוא מכיל כפתור יציאה, וכאשר לוחצים על מקש ה return (כדי להפעיל את הכפתור) האפליקציה מסתיימת. הדרך הקלה ביותר להדר את התכנית היא לקחת את כל מחלקות ה SST (מוצגות בסוף המאמר) ולשים אותן בתוך ספריה. הוסף את הקוד המוצג כאן לקובץ CPP נוסף. הכלל את כל קבצי ה CPP ב project או MAKEFILE והדר וקשר (link) אותם (אם אתה משתמש במהדרים של Visual C++ או Borland, תרצה ליצור אפליקציית מסוף מבוססת טקסט נורמלית). לאחר מכן בצע את האפליקציה. SST מניחה שחלון הפלט מטפל בסדרות תווים ANSI, כך שאם אתה משתמש ב DOS או ב Win 95 בדוק שכיוונת התקן ל ANSI.SYS בקובץ config.sys (במילים אחרות, הוסף "DEVICE=ANCI.SYS" לקובץ config.sys) ואתחל את המחשב. אם אתה משתמש ב NT או UNIX, מסופקות גרסאות מיוחדות של הקבצים המתאימים.

להלן תוכנית קצת יותר מתקדמת שמשתמשת בשתי בקרות שזמינות ב SST (תוויות וכפתורים). תוכנית זו מיישמת אמצעי הגדלה פשוט. כאשר מפעילים את כפתור ה "Increment", המונה גדל באחד. כאשר מפעילים את כפתור היציאה התכנית מסתיימת. מעבירים פוקוס בין שני הכפתורים על ידי הקשה על מקש ה Tab, ומפעילים את הכפתור שבפוקוס עכשיו על ידי הקשה על מקש ה return.

// Counter program

#include <IOSTREAM.H>
#include <STRSTREA.H>

#include "button.h"
#include "input.h"
#include "label.h"
#include "rect.h"
#include "window.h"

class App: public Window
{
    Label *number;
    int count;
public:
    App( char *s, Rect &r );
    virtual void HandleEvent( Event &event );
};

App app( "Test", Rect(1, 1, 80, 24) );

const int QUIT = 100;
const int COUNT = 101;

App::App( char *s, Rect &r ): 
    Window( s, r ), count( 0 )
{
    Insert( new Label("Count: ", 
        Rect(20, 5, 30, 7)) );
    number = new Label("0", 
        Rect(31, 5, 40, 7));
    Insert( number );

    Insert( new Button(" Increment", 
        Rect(28, 18, 40, 20), COUNT) );
    Insert( new Button(" Quit", 
        Rect(50, 18, 57, 20), QUIT) );
}

void main()
{
    app.Execute();
}

 void App::HandleEvent( Event &event)
{
    char t[100];
    ostrstream ostr ( t, 100 );

    Window::HandleEvent( event );

    if( event.type == COMMAND )
    {
        switch( event.message )
        {
            case COUNT:
                count++;
                ostr.width(4);
                ostr << count << ends;                  
                number->SetTitle( t );
                break;

            case QUIT:
                Close();
                screen.Clear();
                break;
        }
    }
    event.type = CLEAR;
}

ההבדל היחיד בין תוכנית זו והתכנית הראשונה הוא העובדה שיותר בקרות מוכנסות לחלון בבנאי החלון, ופונקציית ה HandleEvent מטפלת בשני מאורעות במקום באחד: אחד מגיע מכפתור ה COUNT והשני מכפתור ה QUIT.

תרגיל: תרגיל טוב בנקודה זו יהיה להוסיף לתכנית כפתור חדש בשם "Decrement" שיקטין את המונה. על מנת לעשות זאת, הוסף כפתור חדש (אולי תוך סידור מחדש של יתר הבקרות בתהליך) והוסף פונקצית טיפול במאורעות חדשה. ייתכן שתוסיף כפתור "Clear" שיאפס את המונה.

כדוגמא סופית, הקוד הבא מיישם את תכנית המרת הטמפרטורות שראינו מוקדם יותר. קוד זה מדגים את השימוש בבקרת קלט.

#include <iostream.h>
#include <strstrea.h>

#include "button.h"
#include "input.h"
#include "label.h"
#include "rect.h"
#include "window.h"

class App: public Window
{
    InputLine *fahr;
    Label *cel;
public:
    App( char *s, Rect &r );

    virtual void HandleEvent( Event &event );
};

const int QUIT = 100;
const int CONVERT = 101;

App app( "Test", Rect(1, 1, 80, 24) );
App::App( char *s, Rect &r ): Window( s, r )
{
    fahr = new InputLine ("fahr", Rect(40, 5, 50, 7) );
    Insert( fahr );

    Insert( new Label("Fahrenheit Temperature:   ", Rect(14, 5, 38, 7)) );
    Insert( new Label("Celsius Temperature:", Rect(14, 9, 38, 11)) );
    cel = new Label("0", Rect(40, 9, 45, 11));
    Insert( cel );

    Insert( new Button(" Convert", Rect(28, 18, 40, 20), CONVERT) );
    Insert( new Button(" Quit", Rect(50, 18, 57, 20), QUIT) );
}

void App::HandleEvent( Event &event)
{
    char s[100];
    char t[100];
    float f;
    istrstream istr( s, 100 );
    ostrstream ostr( t, 100 );

    Window::HandleEvent( event );

    if( event.type == COMMAND )
    {
        switch( event.message )
        {
            case CONVERT:
                fahr->GetText( s, 100 );
                istr >> f;
                ostr.width(6);
                ostr.precision(2);
                ostr << (f-32) * 5/9.0 << ends;
                cel->SetTitle( t );
                break;

            case QUIT:
                Close();
                screen.Clear();
                break;
        }
    }
    event.type = CLEAR;
}

void main()
{
    app.Execute();
}

כאשר אתה מריץ תוכנית זו, השתמש במקש ה tab כדי להעביר את הפוקוס בין בקרת הקלט ושני הכפתורים. הקש return כדי להפעיל כפתור. כאשר בקרת הקלט בפוקוס, ניתן להקיש על תווים או על מקש ה backspace.

הבנת הירארכית המחלקות SST

כדי להבין את שלוש התוכניות שראינו לעיל, זה עוזר להתבונן בחלק מהמחלקות בהירארכית SST ולהבין איך הן מתקשרות אחת לשניה. בואו נתחיל במחלקת הבסיס של ההירארכיה: Rect:

class Rect
{
public:
    int top, bottom, left, right;
    Rect();
    Rect( int x1, int y1, int x2, int y2 );
    int Height();
    void SetSize( Rect &r );
    int Width();
};

המחלקה Rect היא מאוד פשוטה (אם תתבונן בקובץ ה CPP בסוף מאמר זה תוכל לראות עד כמה היא באמת פשוטה - כל פונקציה מכילה רק שורה אחת או שתים של קוד). המחלקה מכילה את ארבעת ה data members הדרושים כדי להחזיק את הקואורדינטות העליונות, השמאליות, הימניות והתחתונות של המלבן. בנאי ברירת המחדל קובע את הקואורדינטות האלה לאפס. יש גם בנאי נוסף שמאתחל אותם לערכים מתוארים. הפונקציות החברות Height ו Width מחשבות את הגובה והרוחב של המלבן באמצעות חיסור פשוט. הפונקציה החברה SetSize מאפשרת לך לשנות את הקואורדינטות של המלבן בכל זמן שהוא.

המחלקה Object יורשת מהמחלקה Rect (ניתן לטעון שמחלקת ה Object צריכה להיות מחלקת הבסיס ושצריך להיות לה יחס של "משתמשת ב" עם מחלקת ה Rect במקום היחס "סוג של" שנמצא בהירארכיה זו - זה טיעון טוב, לכן שנה את ההירארכיה אם אתה רוצה). המחלקה Object יודעת איך לטפל במאורעות ולקבל פוקוס.

class Object: public Rect
{
protected:
        char *title;
        int takesFocus;
        int focused;
        Screen screen;

public:
    Object();
    Object( char *s, Rect &r );
    virtual ~Object();
    int AcceptsFocus();
    virtual void Draw();
    virtual void HandleEvent( Event &event );
    virtual void ReleaseFocus();
    virtual void SetFocus();
    virtual void SetTitle( char *s );
};

למחלקה זו שני בנאים, השני הוא זה שמשתמשים בו בדרך כלל. בנאי זה מקבל מחרוזת שפועלת ככותרת לעצם ומלבן ששולט על הגודל והמיקום של האובייקט. אובייקט יכול לקבל פוקוס או לא (תווית היא בקרה שלא מקבלת פוקוס, בעוד שכפתור כן). ניתן לבדוק את הפונקציה AcceptsFocus כדי לבדוק אם העצם מקבל פוקוס או לא. כל העצמים יודעים איך לצייר את עצמם, וגורמים להם לצייר את עצמם על ידי קריאה לפונקציה החברה Draw. פוקציה זו מציירת מלבן סביב העצם. כל האובייקטים גם יכולים לטפל במאורעות, ועושים זאת בפונקציה החברה שלהם HandleEvent. ניתן לתת לעצם פוקוס באמצעות קריאה לפונקציה החברה בו SetFocus ולקחת אותו ממנו באמצעות ReleaseFocus. לבסוף, ניתן לשנות את מחרוזת הכותרת באמצעות SetTitle. התבונן ב OBJECT.CPP ותמצא שכל הפונקציות החברות הן פשוטות ביותר.

מחלקת האובייקט משתמשת במחלקת העזר Screen. מחלקת ה Screen יודעת לצייר על המסך, ומכילה שלוש פונקציות חברות: GotoXY מזיזה את הסמן על המסך, CharXY מציירת תו במיקום מסוים, ו Clear מנקה את כל המסך. עיין ב SCREEN.H כדי לראות כמה המחלקה פשוטה. המחלקה משתמשת ב ANSI escape sequences (רצף תווי בקרה למדפסת) סטנדרטים כדי להזיז את הסמן ולנקות את המסך.

מחלקת החלון יורשת ממחלקת העצם. מחלקת החלון היא עצם, והיא גם מחזיקה רשימה של אובייקטים אחרים. ברשימה נמצאות כל הבקרות הכלולות בחלון. כיוון שהיא מכירה את כל הבקרות שלה, מחלקת החלון יכולה לעשות מספר דברים:

  • היא יכולה לרענן את עצמה באמצעות מעבר על הרשימה וקריאה לפונקציה החברה Draw של כל הבקרות ברשימה.
  • היא יכולה לשנות את הבקרה על ידי פוקוס באמצעות קריאה ל ReleaseFocus על העצם שבפוקוס ול SetFocus על העצם שיקבל פוקוס.
  • היא יכולה להעביר מאורעות שהיא לא יכולה לטפול בהם לבקרה שבפוקוס.

מחלקת החלון מכילה את הפונקציות הבאות:

class Window: public Object
{
protected:
    ObjectList list;
    int running;

public:
    Window();
    Window( char *s, Rect &r );
    ~Window();
    int Close();
    virtual void Draw();
    void Execute();
    virtual void HandleEvent( Event &event );
    void Insert( Object *obj );
    void MoveFocus( char direction );
    void Remove( Object *obj );
    int WindowRunning();
};

ראית בדוגמא תכניות בהן הפונקציה הראשית צריכה לקרוא לפונקצית הביצוע של החלון. פונקציה זו, אם מתבוננים בקוד, פשוט מבצעת בלולאה ומחזירה אחורה הקשות על מקשים, תוך העברתם לפונקציה HandleEvent. הפונקציה HandleEvent מעבירה פוקוס כאשר לוחצים על מקש ה Tab, מסתיימת כאשר לוחצים על מקש ה ESC, או מעבירה את המאורע הלאה לעצם שבפוקוס אחרת.

הפונקציות הוירטואליות נכנסות לתמונה בפונקציות Draw ו HandleEvent. לדוגמא, התבונן בפונקציה Draw במחלקת החלון. היא עושה לולאה על כל העצמים ברשימת העצמים וקוראת לפונקציה Draw. לכל עצם, תלוי בטיפוס שלו (כפתור, קלט, תווית) יש יישום משלו לפונקצית ה Draw כך שכל אחת מציירת את עצמה בהתאם. הפונקציה HandleEvent במחלקת החלון היא דוגמא טובה נוספת. פונקצית ה Execute קוראת ל HandleEvent. כיוון שירשנו ממחלקת החלון, הגרסה שלנו של HandleEvent נקראת ראשונה. בקוד שלנו, אנחנו חוזרים אחורה עד לפונקצית ברירת המחדל Window::HandleEvent כך שהיא יכולה לבצע כל טיפול אוטומטי במקשים TAB או ESC, וגם לתת לאובייקט שבפוקוס מרווח קטן לטיפול בשגיאות שהוא מבין. אם אחד מהשניים יכול לטפל במאורע הוא עושה זאת, והם גם יכולים לנקות את המאורע כדי לסמן אותו כמטופל. כאשר Window::HandleEvent חוזרת, הקוד שלנו יכול לגשת לכל מאורע שנשאר.

אם תעיין ב EVENT.H, תראה שמחלקת העזרevent מכירה רק שלושה סוגי מאורעות:

const char KEYBOARD = 100;
const char COMMAND  = 101;
const char CLEAR = 102;

החלון יכול ליצור מאורעות של הקשת מקש (KEYSTROKE). בקרת כפתורים יכולה לשלוח הודעה של KEYSTROKE למאורע COMMAND. כדי לסמן מאורע כ"מטופל" הוא נקבע ל CLEAR.

פונקצית ה Insert, שנראית גם היא בדוגמא זו, פשוט מכניסה בקרה לתוך רשימת הבקרות. Remove מסוגלת להסיר בקרה מהרשימה.

שלושת הבקרות הן מאוד פשוטות וניתן להבין אותן בקלות על ידי עיון בקבצי ה H ו CPP בסוף מאמר זה. בקרת התוויות מציגה טקסט. ניתן לשנות את הטקסט שלה בכל עת. היא לא מקבלת פוקוס, כך שהיא אף פעם לא מטפלת במאורעות. בקרת הקלט מקבלת הקשות מקש ומציגה אותן. היא מחזיקה את הקשות המקש במחרוזת. ניתן לאתחל אותה באמצעות SetText או להחזיר את המחרוזת באמצעות GetText. מחלקת הכפתור יורשת ממחלקת התווית. מטרתה המרכזית היא לחפש מקשי return ולשלוח את המאורע למאורע של פקודה.

כפי שאתה יכול לראות, לא מעורב כאן הרבה קוד. מה שמעניין זה הדרך שבה הקוד משתלב ליצור שלם שמתייחס לעצמו. כל הירארכית GUI גדולה יותר עובדת בדיוק באותה צורה, אבל ללא ספק מטפלת ביותר יכולות. מה שמעניין, בכל אופן, זה העובדה שהירארכיות גדולות יותר נשענות בדיוק על אותם עקרונות. לדוגמא, פלט של המשתמש מוגבל, באופן כללי, למקלדת ולעכבר. זהו זה. לכן בעוד שמבחר ומגוון של הירארכיות מחלקה יכולים ליישם הרבה יותר ביצועים, הבסיס הוא אותו דבר כמו שנראה כאן ב SST.

הוספת בקרה חדשה

ניתן ללמוד עוד קצת על SST באמצעות יצירת בקרה חדשה. כאן ניצור בקרת קנה מידה ונשנה באמצעותה את תכנית המרת הטמפרטורה. בקרת קנה מידה תראה כך:

+---------------+
|   20          |
| o--x--------o |
| 0         100 |
+---------------+

באמצעות לחיצה על המקשים 'k' ו 'j' קנה המידה יפחת או יעלה ב 1. באמצעות לחיצה על המקשים 'h' ו 'l' הוא יעלה או ירד ב"דרגה" אחת. זה יכול להיות חינוכי מאוד לנסות ליצור בקרה זו בעצמך לפני שתסתכל על הקוד. שאל את עצמך את השאלות הבאות כדי להתחיל:

  • איך מטפלים בתכנון בקרה זו?

  • לאילו data members הוא זקוק?

  • לאילו פונקציות חברות הוא זקוק?

  • איך מתאימים אותו להירארכיה הקיימת?

נסה ליצור בעצמך גרסה של הבקרה באמצעות שימוש בבקרות הקיימות כדוגמא.

להלן הפתרון לבעיה:

SCALE.H
class Scale: public Object
{
    int minValue, maxValue, currentValue, numNotches;
    float notchValue;

public:
    Scale();
    Scale( char *s, Rect &r, int min = 0, int max = 100 );
    void Dec( int number = 1 );
    void DecNotch( int number = 1 );
    virtual void Draw();
    int GetPosition();
    virtual void HandleEvent( Event &event );
    void Inc( int number = 1 );
    void IncNotch( int number = 1 );
    void SetPosition( int value );
};

SCALE.CPP
#include "scale.h"

Scale::Scale() : Object(), minValue(0), maxValue(0),
    currentValue(0), numNotches(0), notchValue(0)
{}

Scale::Scale( char *s, Rect &r, int min, int max )
    : Object( s, r ), minValue(min), maxValue(max),
    currentValue(min), numNotches(0), notchValue(0)
{
    numNotches = right - left - 6;
    notchValue = (maxValue - minValue) / (float)numNotches;
}

void Scale::Dec( int number )
{
    if( currentValue - number >= minValue )
    {
        currentValue -= number;
        Draw();
    }
}

void Scale::DecNotch( int number )
{
    if( (currentValue - notchValue * number) >= minValue )
        currentValue -= notchValue * number;
    else
        currentValue = minValue;

    Draw();
}

void Scale::Draw()
{
    int i, pos;

    Object::Draw();

    // draw the ends of the scale
    screen.CharXY( left+2, top+2, 'O' );
    screen.CharXY( right-2, top+2, 'O' );

    // draw the middle of the scale
    for( i = 0; i <= numNotches; i++ )
    {
        screen.CharXY( left+3+i, top+2, '-' );
    }

    // draw the min and max values of the scale
    screen.GotoXY( left+2, top+3 );
    cout << minValue;

    screen.GotoXY( right-2-2, top+3 );
    cout << setiosflags(ios::right) << setw(3) << maxValue;

    // draw the current position
    pos = (left + 3) +  ((currentValue - minValue) / notchValue);
    screen.CharXY( pos, top+2, 'x' );

    // clear old value and draw new value
    for( i = left+1; i < right; i++ )
        screen.CharXY( i, top+1, ' ' );

    screen.GotoXY( pos-2, top+1 );
    cout << setiosflags(ios::right) << setw(3) << currentValue;
}

int Scale::GetPosition()
{
    return currentValue;
}

void Scale::HandleEvent( Event &event )
{
    if( event.type == KEYBOARD )
    {
        switch( event.message )
        {
            case 'j':
                Dec();
                break;

            case 'k':
                Inc();
                break;

            case 'h':
                DecNotch();
                break;

            case 'l':
                IncNotch();
                break;
        }
    }
}

void Scale::Inc( int number )
{
    if( currentValue + number <= maxValue )
    {
        currentValue += number;
        Draw();
    }
}

void Scale::IncNotch( int number )
{
    if( (notchValue * number + currentValue) <= maxValue )
        currentValue += notchValue * number;
    else
        currentValue = maxValue;

    Draw();
}

void Scale::SetPosition( int value )
{
    if( (value >= minValue) && (value <= maxValue) )
        currentValue = value;

    Draw();
}

F2C2.CPP: תכנית לדוגמא

התוכנית הבאה יוצרת מחדש את תכנית המרת הטמפרטורות שנראתה קודם תוך שימוש בבקרת קנה המידה

#include 
#include 

#include "button.h"
#include "input.h"
#include "label.h"
#include "rect.h"
#include "scale.h"
#include "window.h"

class App: public Window
{
    Label *cel;
    Scale *scale;
public:
    App( char *s, Rect &r );

    virtual void HandleEvent( Event &event );
};

const int QUIT        = 100;
const int CONVERT    = 101;

App app( "Test", Rect(1, 1, 80, 24) );

App::App( char *s, Rect &r ): Window( s, r )
{
    scale = new Scale("scale", Rect(40,4,66,8), 0, 100 );
    Insert( scale );

    Insert( new Label("Fahrenheit Temperature:   ", Rect(14, 5, 38, 7)) );
    Insert( new Label("Celsius Temperature:", Rect(14, 9, 38, 11)) );
    cel = new Label("0", Rect(40, 9, 45, 11));
    Insert( cel );

    Insert( new Button(" Convert", Rect(28, 18, 40, 20), CONVERT) );
    Insert( new Button(" Quit", Rect(50, 18, 57, 20), QUIT) );
}

void App::HandleEvent( Event &event)
{
    char s[100];

    Window::HandleEvent( event );

    if( event.type == COMMAND )
    {
        switch( event.message )
        {
            case CONVERT:
                int f = scale->GetPosition();

                ostrstream ostr ( s, 100 );
                ostr << (f-32) * 5/9.0 << ends;
                cel->SetTitle( s );
                break;

            case QUIT:
                Close();
                screen.Clear();
                break;
        }
    }
    event.type = CLEAR;
}

void main()
{
    app.Execute();
}

סיכום

במאמר זה ראית איך להשתמש בהירארכיית מחלקות GUI פשוטה בשם SST. כמו כן הייתה לך הזדמנות להרחיב את ההירארכיה ולהבין את התפעול הפנימי שלה. אנו מקווים שניסיון זה יעזור לך להבין יותר בקלות הירארכיות מחלקות גדולות יותר בעתיד.

רישום קוד

הרישום הבא מכיל את כל הקוד למחלקות SST.

/* ----------------------------------------------------------------------- *
 * b u t t o n . h
 *
 * buttons are windows that display a label and respond to the enter
 * key when they have focus.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __BUTTON_H
#define __BUTTON_H

#include "keys.h"
#include "rect.h"
#include "label.h"

class Button: public Label
{
    int data;
public:
    Button();

    Button( char *s, Rect &r, int d );

    virtual void Draw();

    virtual void HandleEvent( Event &event );

    void SetData( int d );
};

#endif

 /* ----------------------------------------------------------------------- *
 * b u t t o n . c p p
 *
 * buttons are windows that display a label and respond to the enter
 * key when they have focus.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#include "button.h"

Button::Button(): Label()
{
    takesFocus = 1;
    focused = 0;
}

Button::Button( char *s, Rect &r, int d )
    : data(d), Label( s, r )
{
    takesFocus = 1;
    focused = 0;
}

void Button::Draw()
{
    Label::Draw();
    Object::Draw();
}

void Button::HandleEvent( Event &event )
{
    if( (event.type == KEYBOARD) && (event.message == RETURN) )
    {
        event.type = COMMAND;
        event.message = data;
    }
}

void Button::SetData( int d )
{
    data = d;
}

 /* ----------------------------------------------------------------------- *
 * e v e n t . h
 *
 * class for events
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __EVENT_H
#define __EVENT_H

const char KEYBOARD = 100;
const char COMMAND  = 101;
const char CLEAR    = 102;

class Event
{
public:
    char type;
    char message;

    Event(): type(COMMAND), message(0) {}

    Event( char t, char k ): type(t), message(k) {}
};

#endif

 /* ----------------------------------------------------------------------- *
 * i n p u t . h
 *
 * a simple class to get input from the user.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __INPUT_H
#define __INPUT_H

#include "keys.h"
#include "object.h"
#include "strclass.h"

class InputLine: public Object
{
    String input;

public:
    InputLine();

    InputLine( char *s, Rect &r );

    void GetText( char *s, int n );

    virtual void Draw();

    virtual void HandleEvent( Event &event );

    void SetText( char *s );
};

#endif

 /* ----------------------------------------------------------------------- *
 * i n p u t . c p p
 *
 * a simple class to get input from the user.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#include "input.h"

InputLine::InputLine()
{
    takesFocus = 1;
    focused = 0;
}

InputLine::InputLine( char *s, Rect &r )
    : Object( s, r )
{
    takesFocus = 1;
    focused = 0;
}

void InputLine::GetText( char *s, int n )
{
    strncpy( s, input.GetString(), n );
}

void InputLine::Draw()
{
    int i;

    Object::Draw();
    if( (Height() > 2) && (Width() > 2) )
    {
        screen.GotoXY( left + 1, top + 1 );
        cout << input.GetString();

        for( i = left + input.GetLength(); i < right - 1; i++ )
            cout << " ";
    }
}

void InputLine::HandleEvent( Event &event )
{
    if( event.type == KEYBOARD )
    {
        switch( event.message )
        {
            case BACKSPACE:
                if( input.GetLength() > 0 )
                {
                    screen.GotoXY( left + input.GetLength(), top + 1 );
                    cout << " ";
                    screen.GotoXY( left + input.GetLength(), top + 1 );
                    input.Remove();
                }
                break;

            case RETURN:
                break;

            default:
                if( event.message == 0 )
                    break;
                if( input.GetLength() + left < right - 1 )
                {
                    input.Insert(event.message);
                    screen.GotoXY( left + input.GetLength(), top + 1 );
                    cout << char(event.message);
                }
                break;
        }
    }
}

void InputLine::SetText( char *s )
{
    input.Clear();
    input.Insert( s );
    Draw();
}

 /* ----------------------------------------------------------------------- *
 * k e y s . h
 *
 * defines useful keyboard key codes.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __KEYS_H
#define __KEYS_H

const char BACKSPACE     = 8;
const char TAB          = 9;
const char RETURN         = 13;
const char SHIFT_TAB    = 15;
const char ESC            = 27;

#endif
 /* ----------------------------------------------------------------------- *
 * l a b e l . h
 *
 * a static label class.  labels do not process events.
 * ----------------------------------------------------------------------- */

#ifndef __LABEL_H
#define __LABEL_H

#include 
#include 
#include "object.h"

class Label: public Object
{
public:
    Label();

    Label( char *s, Rect &r );

    virtual void Draw();

    void SetTitle( char *s );
};

#endif

 /* ----------------------------------------------------------------------- *
 * l a b e l . c p p
 *
 * a static label class.  labels do not process events.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#include "label.h"

Label::Label(): Object()
{
    takesFocus = 0;
    focused = 0;
}

Label::Label( char *s, Rect &r )
    : Object( s, r )
{
    takesFocus = 0;
    focused = 0;
}

void Label::Draw()
{
    if( (Height() > 2) && (Width() > 2) )
    {
        screen.GotoXY( left + 1, top + 1 );
        cout << title;
    }
}

void Label::SetTitle( char *s )
{
    Object::SetTitle( s );
    Draw();
}

 /* ----------------------------------------------------------------------- *
 * o b j e c t. h
 *
 * the base class for all screen objects.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __OBJECT_H
#define __OBJECT_H

#include 
#include "event.h"
#include "rect.h"
#include "screen.h"

class Object: public Rect
{
protected:
    char *title;
    char takesFocus;
    char focused;
    Screen screen;

public:
    Object();

    Object( char *s, Rect &r );

    ~Object();

    char AcceptsFocus();

    virtual void Draw();

    virtual void HandleEvent( Event &event );

    virtual void ReleaseFocus();

    virtual void SetFocus();

    virtual void SetTitle( char *s );
};

#endif

 /* ----------------------------------------------------------------------- *
 * o b j e c t . c p p
 *
 * the base class for all screen objects.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#include 
#include "object.h"

Object::Object(): Rect(), title(0), takesFocus(1)
{
}

Object::Object( char *s, Rect &r )
    : Rect( r )
{
    title = new char[ strlen(s) + 1 ];
    strcpy( title, s );
    takesFocus = 1;
}

Object::~Object()
{
    delete [] title;
}

char Object::AcceptsFocus()
{
    return takesFocus;
}

void Object::Draw( )
{
    char style;
    int i;

    if( focused )
        style = '=';
    else
        style = '-';

    for( i = left + 1; i < right; i++ )
    {
        screen.CharXY( i, top, style );
        screen.CharXY( i, bottom, style );
    }
    for( i = top + 1; i < bottom; i++ )
    {
        screen.CharXY( left, i, '|' );
        screen.CharXY( right, i, '|' );
    }
    screen.CharXY( left, top, '+' );
    screen.CharXY( right, top, '+' );
    screen.CharXY( left, bottom, '+' );
    screen.CharXY( right, bottom, '+' );
}

void Object::HandleEvent( Event &event )
{
}

void Object::ReleaseFocus()
{
    focused = 0;
}

void Object::SetFocus()
{
    focused = 1;
}

void Object::SetTitle( char *s )
{
    delete [] title;
    title = new char[ strlen(s) + 1 ];
    strcpy( title, s );
}

 /* ----------------------------------------------------------------------- *
 * o b j l i s t . h
 *
 * a class to manage a list of objects in a window.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __OBJLIST_H
#define __OBJLIST_H

class Object;

typedef struct ObjectNode
{
    Object *object;
    ObjectNode *next;
    ObjectNode *prev;
} ObjectNode;

class ObjectList
{
    ObjectNode *top;
    ObjectNode *bottom;
    ObjectNode *current;

public:
    ObjectList();

    ~ObjectList();

    void Insert( Object *obj );

    Object *GetCurrent();

    Object *GetFirst();

    Object *GetLast();

    Object *GetNext();

    Object *GetPrev();

    void Remove( Object *obj );

    void SetCurrent( Object *obj );
};
#endif

 /* ----------------------------------------------------------------------- *
 * o b j l i s t . c p p
 *
 * implementation of a class to manage a list of objects for a window.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#include "objlist.h"

ObjectList::ObjectList(): top(0), bottom(0), current(0)
{
}

ObjectList::~ObjectList()
{
    current = top;

    while( current != 0 )
    {
        top = current->next;
        delete current;
        current = top;
    }
}

void ObjectList::Insert( Object *obj )
{
    if( obj == 0 )
        return;

    if( top == 0 )
    {
        top = new ObjectNode;

        top->object = obj;
        top->next = 0;
        top->prev = 0;

        bottom = current = top;
    }
    else
    {
        bottom->next = new ObjectNode;
        bottom->next->prev = bottom;
        bottom = bottom->next;
        bottom->object = obj;
        bottom->next = 0;
    }
}

Object *ObjectList::GetCurrent()
{
    if( current == 0 )
        return 0;
    else
        return current->object;
}

Object *ObjectList::GetFirst()
{
    if( top == 0 )
        return 0;

    current = top;
    return top->object;
}

Object *ObjectList::GetLast()
{
    if( bottom == 0 )
        return 0;

    current = bottom;
    return bottom->object;
}

Object *ObjectList::GetNext()
{
    if( current == 0 )
        return 0;

    current = current->next;

    if( current == 0 )
        return 0;
    else
        return current->object;
}

Object *ObjectList::GetPrev()
{
    if( current == 0 )
        return 0;

    current = current->prev;

    if( current == 0 )
        return 0;
    else
        return current->object;
}

void ObjectList::Remove( Object *obj )
{
    ObjectNode *temp;

    if( obj == 0 )
        return;

    temp = top;

    while( (temp != 0) && (temp->object != obj) )
        temp = temp->next;

    if( temp->object == obj )
    {
        if( temp == top )
        {
            top = temp->next;

            if( top == 0 )
                bottom = 0;
            else
                top->prev = 0;

            if( current == temp )
                current = top;
        }
        else if( temp == bottom )
        {
            bottom = temp->prev;

            if( bottom == 0 )
                top = 0;
            else
                bottom->next = 0;

            if( current == temp )
                current = bottom;
        }
        else
        {
            temp->next->prev = temp->prev;
            temp->prev->next = temp->next;

            if( current == temp )
                current = temp->next;
        }

        delete temp;
    }
}

void ObjectList::SetCurrent( Object *obj )
{
    current = top;

    while( (current != 0) && (current->object != obj) )
        current = current->next;

    if( current == 0 )
        current = top;
}

 /* ----------------------------------------------------------------------- *
 * r e c t . h
 *
 * a simple rectangle class.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __RECT_H
#define __RECT_H

class Rect
{
public:
    char top, bottom, left, right;

    Rect();

    Rect( int x1, int y1, int x2, int y2 );

    char Height();

    void SetSize( Rect &r );

    char Width();
};

#endif

 /* ----------------------------------------------------------------------- *
 * r e c t . c p p
 *
 * a simple rectangle class.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#include "rect.h"

Rect::Rect(): top(0), bottom(0),
    right(0), left(0)
{
}

Rect::Rect( int x1, int y1, int x2, int y2 )
    : left(x1), top(y1), right(x2), bottom(y2)
{
}

char Rect::Height()
{
    return bottom - top;
}

void Rect::SetSize( Rect &r )
{
    top = r.top;
    bottom = r.bottom;
    right = r.right;
    left = r.left;
}

char Rect::Width()
{
    return right - left;
}

 /* ----------------------------------------------------------------------- *
 * s c r e e n . h
 *
 * a class to manage an ansi text screen.  allows moving to
 * an x, y location and clearing the screen.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __SCREEN_H
#define __SCREEN_H

#include 


class Screen
{
public:
    Screen( )
    {
    }

    void GotoXY( int x, int y )
    {
        cout << "\033[" << y
            << ";" << x << "H";
    }

    void CharXY( int x, int y, char c)
    {
        GotoXY( x, y );
        cout << c;
    }

    void Clear()
    {
        cout << "\033[2J";
    }
};

#endif

 /* ----------------------------------------------------------------------- *
 * s t r c l a s s . h
 *
 * a simple string class.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __STRCLASS_H
#define __STRCLASS_H

#include 

class String
{
protected:
    char string[80];
    int pos;
public:
    String();

    String( char *s );

    void Clear();

    int GetLength();

    const char *GetString();

    void Insert( char c );

    void Insert( char *s );

    void Remove();
};

#endif

 /* ----------------------------------------------------------------------- *
 * s t r c l a s s . c p p
 *
 * a simple string class.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#include "strclass.h"

String::String(): pos(0)
{
    string[0] = 0;
}

String::String( char *s )
{
    strcpy( string, s );
    pos = strlen( string );
}

void String::Clear()
{
    string[0] = 0;
    pos = 0;
}

int String::GetLength()
{
    return pos;
}

const char *String::GetString()
{
    return string;
}

void String::Insert( char c )
{
    string[pos] = c;
    pos++;
    string[pos] = 0;
}

void String::Insert( char *s )
{
    strcat( string, s );
    pos = strlen( string );
}

void String::Remove()
{
    if( pos > 0 )
    {
        pos--;
        string[pos] = 0;
    }
}

 /* ----------------------------------------------------------------------- *
 * w i n d o w . h
 *
 * the basic window class.  all screen objects are derived from
 * this class.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __WINDOW_H
#define __WINDOW_H

#include     // !!!
#include 
#include 
#include 
#include "event.h"
#include "keys.h"
#include "object.h"
#include "objlist.h"
#include "rect.h"

const char WIN_NEXT    = 1;
const char WIN_PREV    = 2;

class Window: public Object
{
protected:
    ObjectList list;
    char running;
public:
    Window();

    Window( char *s, Rect &r );

    ~Window();

    int Close();

    virtual void Draw();

    void Execute();

    virtual void HandleEvent( Event &event );

    void Insert( Object *obj );

    void MoveFocus( char direction );

    void Remove( Object *obj );

    char WindowRunning();
};

#endif

 /* ----------------------------------------------------------------------- *
 * w i n d o w . c p p
 *
 * the basic window class.  all screen objects are derived from
 * this class.
 * 
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#include "window.h"

Window::Window(): running(1)
{
    takesFocus = 1;
}

Window::Window( char *title, Rect &r )
    : Object( title, r )
{
    running = 1;
    takesFocus = 1;
}

Window::~Window()
{
}

int Window::Close( )
{
    running = 0;
    return 1;
}

void Window::Draw( )
{
    Object *temp, *current = list.GetCurrent();

    SetFocus();
    Object::Draw();

    // draw all the child objects
    temp = list.GetFirst();
    while( temp != 0 )
    {
        temp->Draw();
        temp = list.GetNext();
    }

    list.SetCurrent( current );
}

void Window::Execute( )
{
    Event event;

    list.GetLast();
    MoveFocus( WIN_NEXT );
    screen.Clear();
    Draw();

    do
    {
        event.type = KEYBOARD;
        event.message = getch();
        HandleEvent( event );
    } while( WindowRunning() );
}

void Window::HandleEvent( Event &event )
{
    if( event.type == KEYBOARD )
    {
        switch( event.message )
        {
            case TAB:
            case SHIFT_TAB:
                if( list.GetCurrent() == 0 )
                    break;

                if( event.message == TAB )
                    MoveFocus( WIN_NEXT );
                else
                    MoveFocus( WIN_PREV );

                event.type = CLEAR;
                break;

            case ESC:
                Close();
                break;

            default:
                if( list.GetCurrent() == 0 )
                    break;

                list.GetCurrent()->HandleEvent( event );
                break;
        }
    }
}

void Window::Insert( Object *obj )
{
    list.Insert( obj );
    obj->Draw();
}

void Window::MoveFocus( char direction )
{
    int listScanned = 0;
    Object *current = list.GetCurrent();

    if( current == 0 )
        return;

    current->ReleaseFocus();
    current->Draw();

    do
    {
        if( direction == WIN_NEXT )
            current = list.GetNext();
        else
            current = list.GetPrev();

        if( current == 0 )
        {
            listScanned++;
            if( direction == WIN_NEXT )
                current = list.GetFirst();
            else
                current = list.GetLast();
        }
    } while( (listScanned < 2) && !current->AcceptsFocus() );
    if( current->AcceptsFocus() )
    {
        current->SetFocus();
        current->Draw();
    }
    listScanned = 0;
}

void Window::Remove( Object *obj )
{
    Object *current;

    current = list.GetCurrent();
    if( current )
        current->ReleaseFocus();

    list.Remove( obj );

    current = list.GetCurrent();
    if( current )
        current->SetFocus();

    screen.Clear();
    Draw();
}

char Window::WindowRunning()
{
    return running;
}

אם אתה עובד ב NT, הקבצים הבאים ששונו יעזרו לך (לא הצלחנו לגרום ל command prompt של NT להבין סדרות ANSI).

/* ----------------------------------------------------------------------- *
 * s c r e e n . h
 *
 * a class to manage a Windows NT console.  allows moving to
 * an x, y location and clearing the screen.
 *
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __SCREEN_H
#define __SCREEN_H

#include 

class Screen
{
public:
    Screen();
    ~Screen();
    void GotoXY( int x, int y );
    void CharXY( int x, int y, char c);
    void Clear();
};

#endif

/* ----------------------------------------------------------------------- *
 * s c r e e n . c p p
 *
 * a class to manage an Windows NT console.  allows moving to
 * an x, y location and clearing the screen.
 *
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

// for NT Screen Console functions
#include 
#include "screen.h"

static HANDLE hConsole = 0;
static int instanceCount = 0;

Screen::Screen()
{
    if( instanceCount == 0 )
    {
        hConsole = GetStdHandle( STD_OUTPUT_HANDLE );
    }

    instanceCount++;

    Clear();
}


Screen::~Screen()
{
    instanceCount--;

    if( instanceCount == 0 )
    {
//		  CloseHandle( hConsole );
    }
}


void Screen::GotoXY( int x, int y )
{
    COORD coord;

    coord.X = x - 1;
    coord.Y = y - 1;
	
    SetConsoleCursorPosition( hConsole, coord );
}


void Screen::CharXY( int x, int y, char c)
{
    COORD coord;
    DWORD numWritten;

    coord.X = x - 1;
    coord.Y = y - 1;
	
    SetConsoleCursorPosition( hConsole, coord );
    WriteConsoleOutputCharacter( hConsole, &c, 1, coord, &numWritten );
}


void Screen::Clear()
{
  COORD coordScreen = { 0, 0 }; /* here's where we'll home the cursor */
  BOOL bSuccess;
  DWORD cCharsWritten;
  CONSOLE_SCREEN_BUFFER_INFO csbi; /* to get buffer info */
  DWORD dwConSize; /* number of character cells in the current buffer */

  /* get the number of character cells in the current buffer */
  bSuccess = GetConsoleScreenBufferInfo(hConsole, &csbi);

  dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
  /* fill the entire screen with blanks */
  bSuccess = FillConsoleOutputCharacter(hConsole, (TCHAR) ' ',
      dwConSize, coordScreen, &cCharsWritten);

  /* get the current text attribute */
  bSuccess = GetConsoleScreenBufferInfo(hConsole, &csbi);

  /* now set the buffer's attributes accordingly */
  bSuccess = FillConsoleOutputAttribute(hConsole, csbi.wAttributes,
      dwConSize, coordScreen, &cCharsWritten);

  /* put the cursor at (0, 0) */
  bSuccess = SetConsoleCursorPosition(hConsole, coordScreen);

  return;
}


אם אתה עובד על UNIX, הקבצים הבאים ששונו יעזרו לך

/* ----------------------------------------------------------------------- *
 * v e r s i o n . h
 *
 * defines whether we are building an msdos or unix version
 *
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

//#define SST_UNIX  // uncomment this line to build a UNIX version

/* ----------------------------------------------------------------------- *
 * k e y s . h
 *
 * defines useful keyboard key codes.
 *
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __KEYS_H
#define __KEYS_H

#include "version.h"

const char BACKSPACE = 8;
const char TAB = 9;

#ifdef SST_UNIX
        const char RETURN = 10;
#else
        const char RETURN = 13;
#endif

const char SHIFT_TAB = 15;
const char ESC = 27;

#endif

/* ----------------------------------------------------------------------- *
 * w i n d o w . h
 *
 * the basic window class.  all screen objects are derived from
 * this class.
 *
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#ifndef __WINDOW_H
#define __WINDOW_H

#include "version.h"

#ifdef SST_UNIX
    #include 
    #include 
    #include 
#else
    #include  // dos specific
#endif

#include 
#include 
#include 
#include "event.h"
#include "keys.h"
#include "object.h"
#include "objlist.h"
#include "rect.h"

const char WIN_NEXT	= 1;
const char WIN_PREV	= 2;

class Window: public Object
{
protected:
    ObjectList list;
    int running;

public:
    Window();

    Window( char *s, Rect &r );

    ~Window();

    int Close();

    virtual void Draw();

    void Execute();

    virtual void HandleEvent( Event &event );

    void Insert( Object *obj );

    void MoveFocus( char direction );

    void Remove( Object *obj );

    int WindowRunning();
};

#endif

/* ----------------------------------------------------------------------- *
 * w i n d o w . c p p
 *
 * the basic window class.  all screen objects are derived from
 * this class.
 *
 * Copyright 1996 by Interface Technologies, Inc. All Rights Reserved.
 * ----------------------------------------------------------------------- */

#include "window.h"

#ifdef SST_UNIX
    static struct termio ostate;
#endif

Window::Window(): running( 0 )
{
    #ifdef SST_UNIX
        struct  termio  nstate;

        ioctl(0, TCGETA, &ostate);
        nstate = ostate;
        nstate.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
        nstate.c_cc[VMIN] = 1;
        nstate.c_cc[VTIME] = 0;
        ioctl(0, TCSETAW, &nstate);
    #endif

    takesFocus = 1;
}

Window::Window( char *title, Rect &r )
    : running( 0 ), Object( title, r )
{
    #ifdef SST_UNIX
        struct  termio  nstate;

        ioctl(0, TCGETA, &ostate);
        nstate = ostate;
        nstate.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
        nstate.c_cc[VMIN] = 1;
        nstate.c_cc[VTIME] = 0;
        ioctl(0, TCSETAW, &nstate);
    #endif

    takesFocus = 1;
}

Window::~Window()
{
    #ifdef SST_UNIX
        ioctl( 0, TCSETAW, &ostate );
    #endif
}

int Window::Close( )
{
    running = 0;
    return 1;
}

void Window::Draw( )
{
    Object *temp, *current = list.GetCurrent();

    SetFocus();
    Object::Draw();

    // draw all the child objects
    temp = list.GetFirst();
    while( temp != 0 )
    {
        temp->Draw();
        temp = list.GetNext();
    }

    list.SetCurrent( current );
}

void Window::Execute( )
{
    Event event;

    // make cin and cout work with buffers of length 1
    cin.setf( ios::unitbuf );
    cout.setf( ios::unitbuf  );
    
    // we are now up and running
    running = 1;

    list.GetLast();
    MoveFocus( WIN_NEXT );
    screen.Clear();
    Draw();

    do
    {
        event.type = KEYBOARD;

        #ifdef SST_UNIX
            event.message = getchar();
        #else
            event.message = getch();
        #endif

        HandleEvent( event );
    } while( WindowRunning() );
}

void Window::HandleEvent( Event &event )
{
    if( event.type == KEYBOARD )
    {
        switch( event.message )
        {
            case TAB:
            case SHIFT_TAB:
                if( list.GetCurrent() == 0 )
                    break;

                if( event.message == TAB )
                    MoveFocus( WIN_NEXT );
                else
                    MoveFocus( WIN_PREV );

                event.type = CLEAR;
                break;

            case ESC:
                Close();
                break;

            default:
                if( list.GetCurrent() == 0 )
                    break;

                list.GetCurrent()->HandleEvent( event );
                break;
        }
    }
}

void Window::Insert( Object *obj )
{
    list.Insert( obj );
    if( WindowRunning() )
        obj->Draw();
}

void Window::MoveFocus( char direction )
{
    int listScanned = 0;
    Object *current = list.GetCurrent();

    if( current == 0 )
        return;

    current->ReleaseFocus();
    current->Draw();

    do
    {
        if( direction == WIN_NEXT )
            current = list.GetNext();
        else
            current = list.GetPrev();

        if( current == 0 )
        {
            listScanned++;
            if( direction == WIN_NEXT )
                current = list.GetFirst();
            else
                current = list.GetLast();
        }
    } while( (listScanned < 2) && !current->AcceptsFocus() );

    if( current->AcceptsFocus() )
    {
        current->SetFocus();
        current->Draw();
    }
    listScanned = 0;
}

void Window::Remove( Object *obj )
{
    Object *current;

    current = list.GetCurrent();
    if( current )
        current->ReleaseFocus();

    list.Remove( obj );

    current = list.GetCurrent();
    if( current )
        current->SetFocus();

    screen.Clear();
    Draw();
}

int Window::WindowRunning()
{
    return running;
}