Страница 1 из 2

Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 12:25:34
xterro
Доброго времени суток, продолжаю постигать написание приложений по фен-шую из ничего, итак: имеется некоторое приложение, которое, хочется чтобы было кроссплатформенным. Для этого я создал класс TApplication, в котором вызываю функцию создания главного окна. В этом окне создаётся контекст OpenGL, в котором всё и будет рисоваться. Соответсвенно под windows или под Linux процесс инициализации и функции создания вего этого опенджиэльного хозяйства разные, поэтому я решил их раскидать по разным модулям(для windows назвал его tr_windows), каждом из которых сделал "общий нтерфейс", дабы вызывать их основного класса TApplication. Проблема теперь в том, что есть еще такой класс TCore, который предназначен для того чтобы собственно и делать всю основную работу приложения(рисовать в окне, обрабатывать нажатия кнопок и т.д), но основной то цикл обработки сообщений у меня в модуле tr_windows и вот как вытащить обработку сообщений в TCore, ума не приложу. Може как-то передавать указатели на методы TCore в функцию создания окна? Немного кода:

Код: Выделить всё
{$mode objfpc}{$h+}

unit tr_application;

interface
   
    uses
        {$ifdef windows}
            tr_windows,
        {$endif}
        classes, tr_logger;

    type   
        TApplication = class
        private
            fwindow_name    : string;
            fwindow_width   : integer;
            fwindow_height  : integer;
            ffullscreen     : boolean;
            fparams         : tstringList;
           
            flog            : tlogger; static;
        public
           
            property window_width   : integer   read fwindow_width  write fwindow_width;
            property window_height  : integer   read fwindow_height write fwindow_height;
            property window_name    : string    read fwindow_name   write fwindow_name;
       
            constructor     create();
            destructor      destroy();
            class function  get_instance() : tapplication;
            function        create_window(app_name : pchar; awindow_width, awindow_height : longint; afullscreen : boolean ) : boolean;
            procedure       parse_params(aparams : string);
            procedure       main_loop();
        private
           


        end;
   
implementation   
       
    var
        app : TApplication = nil;
   
    constructor TApplication.create();
    begin
        fwindow_name    := 'tr_application_empty';
        fwindow_width   := 800;
        fwindow_height  := 600;
        ffullscreen     := false;
        flog            := tlogger.create('TEST_LOG.TXT');
    end;
   
    destructor TApplication.destroy();
    begin
        kill_gl_window();
    end;
   
    class function TApplication.get_instance() : tapplication;
    begin
        if(app = nil) then
            app := tapplication.create();
       
        result := app;
        flog.write('Application has been created...');
    end;
   
   
    function TApplication.create_window(app_name : pchar; awindow_width, awindow_height : longint; afullscreen : boolean ) : boolean;
    begin
        if app_name <> '' then
            fwindow_name := app_name;
       
        fwindow_width   := awindow_width;
        fwindow_height  := awindow_height;
        ffullscreen     := afullscreen;
        // следующая функция создаёт само окно с opengl контекстом, но в нём же и обработка всех событий, как бы их вытащить?
        result := create_gl_window(pchar(fwindow_name), fwindow_width, fwindow_height, ffullscreen);
    end;
   
   
    procedure TApplication.main_loop();
    begin
        process_messages();
    end;
   
   
    procedure TApplication.parse_params(aparams : string);
    begin
   
    end;
   
end.


Модуль создания окна, для Windows, под Linux планировался такой же модуль, с такими же функциями:
function create_gl_window(app_name : pchar; wnd_width, wnd_height : longint; app_fullscreen : boolean) : boolean;
procedure kill_gl_window();
procedure process_messages();

только другой реализацией.

Код: Выделить всё
{$mode objfpc}{$h+}
{$apptype gui}

unit tr_windows;

interface

    uses
        gl, glu, windows;
     
    var
        msg       : tmsg;   // Windows messages
        hwindow   : hwnd;   // Windows Handle to the OGL Window
        dc_window : hdc;   // Device Context for the OGL Window
        rc_window : hglrc;   // Render Context for the OGL Window
   
        width, height : longint; 
        fullscreen, active : boolean;
   
    function    create_gl_window(app_name : pchar; wnd_width, wnd_height : longint; app_fullscreen : boolean) : boolean;
    procedure   kill_gl_window();
    procedure   process_messages();

implementation 
 
    procedure gl_init();
    begin
        glShadeModel(GL_SMOOTH);
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
        glClearColor( 0.0, 0.0, 0.0, 1.0 );
        glTranslatef(0.0, 0.0, -1.0);
    end;
   
    procedure gl_resize(awidth : integer; aheight : integer);
    var
        ar : real;
    begin
        if (aheight = 0) then                                       
            aheight := 1;                                         
       
        width := awidth;
        height := aheight;
       
        glViewport(0, 0, awidth, aheight);                     
        glMatrixMode(GL_PROJECTION);                           
        glLoadIdentity();                                       
       
        ar := awidth/aheight;   
        glOrtho(0, 10000, 0, 10000, 0.0, 0.0);
       
        glMatrixMode(GL_MODELVIEW);                             
        glLoadIdentity;   
    end;
   
    procedure gl_render();
    var
        i, x, y, segments_amount : integer;
        angle, r, dx, dy : real;
    begin
        glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
   
        { draw here }
        { тут просто рисование, рисуем бублик и две полоски }
        glClear( GL_COLOR_BUFFER_BIT );
        glLoadIdentity();
       
        glColor3f(0.0, 0.0, 1.0);
        glBegin(GL_LINES);
            glVertex2f(0.0, 0.0);
            glVertex2f(0.0, 1.0);
        glEnd();
       
        glColor3f(1.0, 0.0, 0.0);
        glBegin(GL_LINES);
            glVertex2f(0.0, 0.0);
            glVertex2f(1.0, 0.0);
        glEnd();
       
        glColor3f(1.0, 1.0, 0.0);
       
       
       
        i := 0;
        r := 0.25;
        x := 0;
        y := 0;
       
        segments_amount := 32;
        glBegin(GL_LINE_LOOP);
            for i := 0 to segments_amount do begin
                angle := 2.0 * 3.1415926 * i / segments_amount;
                dx := r * cos(angle);
                dy := r * sin(angle);
                glVertex2f(x + dx, y + dy);
            end;
        glEnd();
       
        r := r/2;
        glBegin(GL_LINE_LOOP);
            for i := 0 to segments_amount do begin
                angle := 2.0 * 3.1415926 * i / segments_amount;
                dx := r * cos(angle);
                dy := r * sin(angle);
                glVertex2f(x + dx, y + dy);
            end;
        glEnd();
       
       
       
        swapBuffers(dc_window);   // put opengl stuff to screen
    end;
   
   
    procedure throw_error(pcErrorMessage : pchar);
    begin
        messageBox(0, pcErrorMessage, 'Error', MB_OK);
        halt(0);
    end;
   
   
    { Функция обработки сообщений}
    function wnd_proc(window: hwnd; amessage, wparam, lparam: longint): longint; stdcall; export;
    begin
        result := 0;

        case amessage of
            WM_CREATE:
            begin
                active := true;      // if GL Window was created correctly, then set
                exit;         // active-flag to "true".
            end;
           
            WM_PAINT:
            begin
                gl_render();
                exit;         // nothing to paint to Windows as we do all drawing with OGL
            end;
           
            WM_SIZE:
            begin
                gl_resize(LOWORD(lparam), HIWORD(lparam));
                gl_render();
                active := true;
                exit;
            end;
           
            WM_MOVE:
            begin
                exit;
            end;
           
            WM_ERASEBKGND:
            begin
                exit; // чтобы не перезаливалось окно и не было мерцания
            end;

            WM_KEYDOWN:
            begin
                if wparam = VK_ESCAPE then
                    sendMessage(hwindow, wm_destroy,0,0);
                exit;         // check for ESC key. If pressed, then send quit message
            end;

            WM_DESTROY:
            begin
                active := false;   // if quit message was sent, exit the main loop by setting
                postQuitMessage(0);   // the active-flag to "false".
                kill_gl_window();
                exit;
            end;

            WM_SYSCOMMAND:      // system wants something..
            begin
                case (wparam) of
                    SC_SCREENSAVE : begin      // ..don't start any screensavers.
                        result := 0;
                    end;

                    SC_MONITORPOWER : begin   // ..and don't kill monitor power.
                        result := 0;
                    end;
                end;
            end;
           
            WM_GETMINMAXINFO:
            begin
                with pMinMaxInfo(lparam)^ do begin
                    ptMinTrackSize.x := 800;
                    ptMinTrackSize.y := 600;
                end;
                exit;
            end;
           
        end;
        result := defWindowProc(window, amessage, wparam, lparam);   // let Windows deal with the rest of the messages.
    end;
   
    function register_window: boolean;
    var
        window_class : wndClass;
    begin
        window_class.style           := CS_HREDRAW or CS_VREDRAW;
        window_class.lpfnWndProc     := wndProc(@wnd_proc);          // Handle to our Windows messaging interface func.
        window_class.cbClsExtra      := 0;
        window_class.cbWndExtra      := 0;
        window_class.hInstance       := system.mainInstance;         // Get the Windows Instance for our app.
        window_class.hIcon           := loadIcon(0, IDI_APPLICATION);
        window_class.hCursor         := loadCursor(0, IDC_ARROW);
        window_class.hbrBackground    := getStockObject(WHITE_BRUSH);
        window_class.lpszMenuName    := nil;
        window_class.lpszClassName   := 'GLWindow';                 // Name the specified Window Class
       
        result := registerClass(window_class) <> 0;
    end;

    { Создаём пустое окно }
    function create_empty_window(app_name : pchar; visible : boolean = true): hwnd;
    var
        hwindow: hwnd;              // Handle to Window
        //dmScreenSettings : DEVMODE;   // Used for Full Screen Mode
    begin
        hwindow := createWindow('GLWindow',
                  app_name,
                  WS_OVERLAPPEDWINDOW or WS_CLIPCHILDREN or WS_CLIPSIBLINGS,
                  cw_useDefault,
                  cw_useDefault,
                  width,
                  height,
                  0, 0,
                  system.mainInstance,
                  nil);

        if hwindow <> 0 then
        begin
            if visible then begin
                showWindow(hwindow, CmdShow);
                updateWindow(hwindow);
            end;
        end;
        create_empty_window := hwindow;
    end;
   
   
    { Init the Window and bind OGL to it. }
    function init_window(hparent : hwnd): boolean;
    var
        function_error : integer;
        pfd : PIXELFORMATDESCRIPTOR;
        iformat : integer;   // Pixel Format

    begin
        function_error := 0;

        dc_window := getDC( hParent );   // Get Device Context

        fillChar(pfd, sizeof(pfd), 0);   // Define Pixel Format
        pfd.nSize         := sizeof(pfd);
        pfd.nVersion      := 1;
        pfd.dwFlags       := PFD_SUPPORT_OPENGL or PFD_DRAW_TO_WINDOW or PFD_DOUBLEBUFFER;
        pfd.iPixelType    := PFD_TYPE_RGBA;
        pfd.cColorBits    := 32;
        pfd.cDepthBits    := 32;
        pfd.iLayerType    := PFD_MAIN_PLANE;

        iformat := choosePixelFormat(dc_window, @pfd);   // Create Pixel Format

        if (iformat = 0) then
            function_error := 1;

        setPixelFormat(dc_window, iformat, @pfd);      // Set Pixel Format
        rc_window := wglCreateContext(dc_window);      // Create OpenGL Context

        if (rc_window = 0) then
            function_error := 2;

        wglMakeCurrent(dc_window, rc_window);      // Bind OpenGL to our Window

        if function_error = 0 then
            init_window := true
        else
            init_window := false;
    end;
   
   
    { Kill Application Window again. }
    procedure kill_gl_window();
    begin
        wglMakeCurrent(dc_window, 0);   // Kill Device Context
        wglDeleteContext(rc_window);   // Kill Render Context
        releaseDC(hWindow, dc_window);   // Release Window
        destroyWindow(hwindow);      // Kill Window itself
    end;

   
    { ***************************************************************************************************** }
   
    procedure process_messages();
    begin
        repeat            // start main proc
            if peekMessage(@msg,0,0,0,0) = true then
            begin
                getMessage(@msg,0,0,0);
                translateMessage(msg);
                dispatchMessage(msg);
            end;
        until active = false;      // end main proc
    end;
   
   
    function create_gl_window(app_name : pchar; wnd_width, wnd_height : longint; app_fullscreen : boolean) : boolean;
    begin
        width       := wnd_width;
        height      := wnd_height;
        fullscreen  := app_fullscreen;

        if not register_window() then begin
            throw_error('Could not register the Application Window!');
            result := false;
            exit;
        end;

        hwindow := create_empty_window(app_name);
        if longint(hwindow) = 0 then begin
            throw_error('Could not create Application Window!');
            result := false;
            exit;
        end;

        if not init_window(hwindow) then begin
            throw_error('Could not initialise Application Window!');
            result := false;
            exit;
        end;
       
        gl_init();
        result := true;
    end;
   
end.


А вот сюда бы передать управление, чтобы обрабатывать все события от кона, нажатия клавиш на клавиатуре и обработку мыши:
Код: Выделить всё
{$mode objfpc}{$h+}
unit tr_core;


interface
    uses
        tr_application;

    type
        TCore = class
        private
            fapp            : tapplication;
            fscene_width    : integer;
            fscene_height   : integer;
           
        public
            constructor create();
            destructor  destroy();
           
            procedure on_key_pressed(key : integer);
            procedure on_key_released(key : integer);
           
            procedure on_mouse_move(x : integer; y : integer);
            procedure on_mouse_down(x : integer; y : integer; button : integer);
            procedure on_mouse_up(x : integer; y : integer; button : integer);
            procedure on_double_click(x : integer; y : integer; button : integer);
            procedure on_mouse_wheel(dx : integer; dy : integer);
            procedure on_resize(width : integer; height : integer);
           
            procedure render();
       
        end;



implementation
   
    constructor TCore.create();
    begin
   
    end;
   
   
    destructor TCore.destroy();
    begin
   
    end;
   
   
    procedure TCore.on_key_pressed(key : integer);
    begin
   
    end;
   
    . . .
   
   
    procedure TCore.render();
    begin
   
    end;

end.




Ну и основной код прилоежения:
Код: Выделить всё
program ttt;
uses
    tr_application;

var
    app : tapplication;
begin
    app := tapplication.get_instance();
    app.create_window('', 1024, 768, false);
    app.main_loop();
   
    app.destroy();
end.

У кого какие мысли? :roll:

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 12:55:47
zub
>>У кого какие мысли?
Вариантов много, но они все различные под win-qt-gtk, гляди как это реализовано в lcl.

Я для винапи когдато использовал GWLP_USERDATA - привязывал к окнам уникальный идентификатор (можно напрямую адрес инстанса класса с реализацией) по нему в цикле обработки сообщений восстанавливал класс окна и вызывал соответственные onЧетоТам

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 15:05:40
xterro
Хм, думаю, может как-то сам модуль переписать, типа определить функции не простым набором, а обернуть всё это дело в класс... в общем пошёл думать :)

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 15:36:20
Дож
Я это реализовал классом-обёрткой над сообщением:
Код: Выделить всё
PWindowEvent = ^TWindowEvent;
TWindowEvent = object
private
{$IF Defined(WINDOWS)}
  // TODO: windows types instead of Cardinal and Integer for cross-platform
  Msg: Cardinal;
  wParam: Integer;
  lParam: Integer;
{$ELSEIF Defined(ANDROID)}
  Event: PAInputEvent;
  EventType: TInt32;
...
{$ENDIF}
public
  function IsKeyDown: Boolean; overload; inline;
  function IsKeyDown(_Key: Integer): Boolean; overload; inline;
  function IsKeyUp: Boolean; overload; inline;
  function IsKeyUp(_Key: Integer): Boolean; overload; inline;
  function Key: Integer; inline;

  function IsChar: Boolean; inline;

  function IsLeftClickDown: Boolean; inline;
  function IsLeftClickUp: Boolean; inline;
  function IsRightClickDown: Boolean; inline;
  function IsRightClickUp: Boolean; inline;
  function IsMiddleClickDown: Boolean; inline;
  function IsMiddleClickUp: Boolean; inline;
  function IsDoubleLeftClick: Boolean; inline;
...


В клиентском коде реализуется коллбек-обработчик с такой сигнатурой:
Код: Выделить всё
TOnWindowEvent = function (Event: PWindowEvent): Boolean of object;

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 15:48:16
zub
>>Хм, думаю, может как-то сам модуль переписать
Не надо ничего переписывать, надо по идентификатору окна которому пришло сообщение восстановить инстанс класса его представляющего и вызвать соответствующий метод метод этого класса.
можно гдето хранить соответствие идентификатор<->класс, можно заюзать GWLP_USERDATA возможно еще ка кто можно исхитриться - но всё это будет платформенозависимо.
Получится чтото вроде:
Код: Выделить всё
function wnd_proc(window: hwnd; amessage, wparam, lparam: longint): longint; stdcall; export;
var
    innstance:TCore;
begin
    instance:=GetMyInstanceFromHWND(window);
    case amessage of
           ...
            WM_MOVE:
            begin
                instance.MouseMowe(...);
            end;
           ...

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 15:53:18
Дож
А, для этих целей да, нужно UserData использовать
Код: Выделить всё
SetWindowLongA(hWnd, GWL_USERDATA, Cardinal(Self));
...
Window := TWindow(GetWindowLongA(hWnd, GWL_USERDATA));

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 16:57:28
xterro
надо по идентификатору окна которому пришло сообщение восстановить инстанс класса его представляющего и вызвать соответствующий метод метод этого класса.

Хм, так может мне так и передавать экземпляр TCore в функцию
create_gl_window(pchar(fwindow_name), fwindow_width, fwindow_height, ffullscreen);
(ту саму, которая непосредственно и создаёт окно) добавить ещё один параметр и передавать? Просто я ума не приложу, как реализовать GetMyInstanceFromHWND(window);где она должна объявляться и что получится с типом данных параметра(в винде это HWND в linux-е ещё что-нибудь) Если напрямую передавать, не придётся реализовывать GetMyInstanceFromHWND :?

А, для этих целей да, нужно UserData использовать

Я так понимаю, это Windows-specific, т.е заточено только под винду?

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 19:59:19
WAYFARER
xterro писал(а):что получится с типом данных параметра(в винде это HWND в linux-е ещё что-нибудь)

THandle?

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 21:04:01
zub
>>Просто я ума не приложу, как реализовать GetMyInstanceFromHWND(window)
В случае использования GWL_USERDATA вот так:
Код: Выделить всё
function GetMyInstanceFromHWND(window: hwnd):TMyWinClassType
begin
  result := TMyWinClassType(GetWindowLongA(window, GWL_USERDATA));
end;


а при создании окна в месте где уже известен и hwnd окна и класс "реализующий" это окно, надо выполнить
Код: Выделить всё
SetWindowLongA(hWnd, GWL_USERDATA, Cardinal(Self){это если внутри класса, если нет то тут будет не селф а соответствующая переменная});

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 22:09:01
xterro
Идея конечно хорошая. только если у меня нет класса окна? Лепить свой и оборачиваnь в него? По сути я пишу это на "чистом" FP без Lazarus-a с его LCL, just for fun :?

P.S. Была ещё такая мысль: объявить в модуле tr_windows функцию, что-то типа
Код: Выделить всё
// раннее определяем все указатели на функцию с соответсвующей сигнатурой
// для всех основных событий: мышка, нажатие клавиши, изменение размеров окна

procedure SetCallback(msg_type : string; callback : FuncPtr);
begin
    if msg_type = "mouse_up" then
        указатель_на_функцию := callback
    else if msg_type = "mouse_move" then ...

   end;     
end;

// далее в wnd_proc вызывается функция по этому казателю


А в классе приложения(TApplication) уже связать функции-обработчики из класса TCore с этими указателями при помощи этой функции.
Как-то так :shock:

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 22:21:10
zub
А что такое у тебя TCore? если это не класс реализации конкретного окна, то я тебя не понял))
У тебя какойто особенно феншуйный феншуй

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 22:43:05
xterro
В TCore я предполагал собственно производить всё рисование в окне и обработку событий окна. Наверно я изначально взял не очень удачный пример для подражания https://github.com/sysint64/e2dit
Т.е это класс реализации функционала, само же окно создаётся обычными winapi-шными функциями и вот теперь надо его состыковать с классом, реализующем функционал... намудрил я вобщем чёта :D

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 12.11.2015 23:09:23
zub
>>всё рисование в окне и обработку событий окна
т.е. ты хочешь работать с всего одним окном? Или это какаято реализация своих "несистемных" окон?

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 13.11.2015 01:26:56
Mirage
xterro писал(а):как вытащить обработку сообщений в TCore


Можно собирать сообщения в системозависимых обработчиках (wnd_proc), конвертировать в системонезависимый вид и складывать куда-нибудь.
А TCore уже когда ему удобно достанет и обработает, знать не зная даже на какой системе работает.

Re: Как вытащить обработку сообщений "наружу" из модуля?

СообщениеДобавлено: 13.11.2015 08:21:42
xterro
т.е. ты хочешь работать с всего одним окном? Или это какаято реализация своих "несистемных" окон?

Да, предполагал работать с одним окном, всё остальное, контролы, дочерние окна, диалоги - рисовать самостоятельно. Т.е в терминах ОС, получается что будет всего одно окно.
Идея была как в UI библиотеке AGAR, там создаётся одно главное окно с opengl контекстом, а все кнопочки, ползунки, диалоги, дочерние окна нарисованы на opengl(http://libagar.org/). Хотя, при таком подходе есть и минусы, например, если нарисовать диалог или дочернее окно, его нельзя будет вынести за границы основного окна :(

Можно собирать сообщения в системозависимых обработчиках (wnd_proc), конвертировать в системонезависимый вид и складывать куда-нибудь.
А TCore уже когда ему удобно достанет и обработает, знать не зная даже на какой системе работает.

Надо же как-то TCore оповестить о том что событие произошло.