Jump to content
shineworld

Add an extra Delphi Control in a new package

Recommended Posts

Hi all.

 

To complete my first Python + DelphiVCL program I need to expose to Python an extra Image Viewer Control.

 

I don't want to create a custom delphivcl.pyd which is a good thing remains original and installable with pip install delphivcl,

so I've tried to add the component in a custom package.

 

Well, seems simple to do but does not work fine...

 

The control to expose is TImgView32 which inherits from:
TImgView32->TCustomImgView32->TCustomImage32->TCustomPaintBox32->TCustomControl

 

so it is close to TLabel and looking at DelphiVCL code I've made same steps:

library cnc_vision_ext;

uses
  osPyCncVisionExt in 'sources\osPyCncVisionExt.pas';

exports
  PyInit_cnc_vision_ext;

{$E pyd}

begin
end.

 

unit osPyCncVisionExt;

interface

uses
  PythonEngine;

function PyInit_cnc_vision_ext: PPyObject; cdecl;

implementation

uses
  System.Classes,
  System.SysUtils,
  System.Variants,
  Winapi.Windows,
  Vcl.ExtCtrls,

  VarPyth,
  WrapDelphi,
  WrapVclExtCtrls,
  WrapVclControls,
  WrapDelphiClasses,

  GR32_Image;
  
type
  TPyDelphiImgView32 = class(TPyDelphiControl)
  private
    function GetDelphiObject: TImgView32;
    procedure SetDelphiObject(const Value: TImgView32);
  public
    class function DelphiObjectClass: TClass; override;
    property DelphiObject: TImgView32 read GetDelphiObject write SetDelphiObject;
  end;

  TPyExtensionManager = class
  private
    FEngine: TPythonEngine;
    FModule: TPythonModule;
    FWrapper: TPyDelphiWrapper;
  public
    procedure WrapperInitializationEvent(Sender: TObject);
  end;

var
  ExtensionManager: TPyExtensionManager;

{ module import functions }

function PyInit_cnc_vision_ext: PPyObject;
begin
  Result := nil;
  try
    ExtensionManager.FEngine := TPythonEngine.Create(nil);
    ExtensionManager.FEngine.AutoFinalize := False;
    ExtensionManager.FEngine.UseLastKnownVersion := True;
    ExtensionManager.FEngine.LoadDllInExtensionModule();

    ExtensionManager.FModule := TPythonModule.Create(nil);
    ExtensionManager.FModule.Engine := ExtensionManager.FEngine;
    ExtensionManager.FModule.ModuleName := 'cnc_vision_ext';

    ExtensionManager.FWrapper := TPyDelphiWrapper.Create(nil);
    ExtensionManager.FWrapper.Engine := ExtensionManager.FEngine;
    ExtensionManager.FWrapper.Module := ExtensionManager.FModule;

    ExtensionManager.FModule.Initialize;
    ExtensionManager.FWrapper.OnInitialization := ExtensionManager.WrapperInitializationEvent;
    ExtensionManager.FWrapper.Initialize;

    Result := ExtensionManager.FModule.Module;
  except
  end;
end;

{ TPyDelphiImgView32 }

class function TPyDelphiImgView32.DelphiObjectClass: TClass;
begin
  Result := TImgView32;
end;

function TPyDelphiImgView32.GetDelphiObject: TImgView32;
begin
  Result := TImgView32(inherited DelphiObject);
end;

procedure TPyDelphiImgView32.SetDelphiObject(const Value: TImgView32);
begin
  inherited DelphiObject := Value;
end;

{ TPyExtensionManager }

procedure TPyExtensionManager.WrapperInitializationEvent(Sender: TObject);
begin
  FWrapper.RegisterDelphiWrapper(TPyDelphiImgView32);
end;

initialization
  ExtensionManager := TPyExtensionManager.Create;

finalization
  ExtensionManager.Free;

end.

Well, compilation OK and import on Python OK,

but when I try to create the object assigning the parent I got that:

 

from delphivcl import *
from cnc_vision_ext import *

class TestForm(Form):

    def __init__(self, owner):

        # print type of self ('__main__.TestForm')
        print(type(self))

        # create a vcl label and assign parent: WORKS
        self.label = Label(self)
        self.label.Parent = self
        self.label.Left = 10
        self.label.Top = 10
        self.label.Caption = 'Hello World'


        # create a ext image and assign parent: ERROR
        self.image = ImgView32(self) # <-- AttributeError: Owner receives only Delphi objects
        self.image.Parent = self
        self.image.Left = 10
        self.image.Top = 30
        self.image.Width = 200
        self.image.Height = 100

def main():
    Application.Initialize()
    Application.Title = 'test'
    MainForm = TestForm(Application)
    MainForm.Show()
    FreeConsole()
    Application.Run()

if __name__ == '__main__':
    main()

image.thumb.png.37d6be3dad92a96e45a9f2da9741085c.pngù

 

If I check with a Python console the types seem very close:

D:\x\develop\qem\cnc_vision_1>python
Python 3.9.12 (tags/v3.9.12:b28265d, Mar 23 2022, 23:52:46) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

>>> from delphivcl import *
>>> from cnc_vision_ext import *

>>> frm = Form(None)

>>> lbl = Label(frm)
>>> lbl.Parent = frm

>>> type(lbl)
<class 'Label'>
>>> lbl.__doc__
'Wrapper for Delphi TLabel\n'
>>> lbl.ClassName
'TLabel'

>>> img = ImgView32(frm) # does not work with frm as like as lbl
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Owner receives only Delphi objects

>>> img = ImgView32(None) # try with none to check object type

>>> type(img)
<class 'ImgView32'>
>>> img.__doc__
'Wrapper for Delphi TImgView32\n'
>>> img.ClassName
'TImgView32'


Thank you in advance for any suggestion 🙂
 

Share this post


Link to post

TPythonEngine and TPyDelphiWrapper are singleton.  You should not have two in the same application.   Better to add your control wrapper to delphivcl and recompile.

Edited by pyscripter

Share this post


Link to post

Ah, I missed this thing ... so I got the whole structure of my project wrong 😞

 

So far what I had done:

- Python program developed with PyScripter.
- Using DelphiVCL (pip install delphivcl) as the main UI.
- Using P4D to create a new extension module in Delphi (cnc_vision_ext.pyd) where I added new features and where I "wanted" to add new graphic components as well, eg: TImgView32.

 

Having the cnc_vision_ext module is due to the need that some features I currently use are only available in Delphi and are not in Python and I wanted to "port" them to Python.

This module uses its own TPythonEngine, TPythonModule, and TPyDelphiWrapper.

 

So far it has worked but everything was not foreseen in the P4D structure could it run into malfunctions?

So far it seems to me everything has worked correctly but with only one trick to add a function that updates an Image (TImage) created in Python with a bytes array that did not recognize the type passed as Delphi and I went around it.

 

For example in the function update_image_from_bytes -> CncVisionUpdateImageFromBytesArray_Wrapper I've worked around a Image (TImage) created with delphivcl and passed as argument to recover Delphi wrapped object

and so permits to assign it an image using a byte array containing the bitmap elaborated with Python....

I hope I have not completely misunderstood how to use DelphiVCL and P4D to add new extra features ...

cnc_vision_video_ext.zip

Share this post


Link to post
11 hours ago, pyscripter said:

TPythonEngine and TPyDelphiWrapper are singleton.  You should not have two in the same application.   Better to add your control wrapper to delphivcl and recompile.

Following your info, I've created a custom DelphiVCL with third parties VCL objects, and now WORKS perfectly.
This was the occasion to add your fix for memory leaks on property sets.
I will test the behavior in the next few days.

image.thumb.png.1bbf06221d344719a1b3215a2d7507c4.png

 

Thank you very much for your help!

  • Like 1

Share this post


Link to post

Ok, I've added my first extension to DelphiVCL to use Graphics32 TImgView32 instead of standard Delphi TImage which suffers from blinking effects,

during the repaint phase (not an issue on Delphi but inherited from OS level) when bitmap images do not fully cover the Image area.

It is only an early implementation... I'm not a guru of P4D unfortunately, but it works.

{***
 *  TAKE CARE
 *  =========
 *  This units add extra third parties components from Graphics32.
 *
 *  https://github.com/graphics32/graphics32
 *
 *  LICENSE
 *  =======
 *  As of version 1.5.1b Graphics32 is licensed under the terms of the Mozilla Public License (MPL) 1.1.
 *  You may obtain a copy of the License at http://www.mozilla.org/MPL
 *
 *  Starting with version 1.9 Graphics32 is also licensed under the Lesser General Public License (LGPL) 2.1
 *  with linking exception.
 *
 *  You may use the files in this distribution under the terms of either the MPL 1.1 or the LGPL 2.1 with linking
 *  exception. You can find a copy of both licenses in the plain text file License.txt which is located in the root
 *  directory of the Graphics32 distribution package.
 *
 **}

{$I ..\Definition.Inc}

unit WrapExtGraphics32;

interface

uses
  System.Classes,
  System.TypInfo,
  System.SysUtils,
  Winapi.Windows,
  Vcl.Controls,
  Vcl.Graphics,
  Vcl.StdCtrls,

  WrapDelphi,
  PythonEngine,
  WrapVclControls,
  WrapDelphiClasses,

  GR32_Image,
  GR32_Layers;

type
{
  TMouseEvent = procedure(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer) of object;
  TImgMouseEvent = procedure(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer) of object;
}

  { TImgMouseEvent wrapper }

  TImgMouseEventHandler = class(TEventHandler)
  protected
    procedure DoEvent(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer; Layer: TCustomLayer);
  public
    constructor Create(PyDelphiWrapper : TPyDelphiWrapper; Component : TObject; PropertyInfo : PPropInfo; Callable : PPyObject); override;
    class function GetTypeInfo : PTypeInfo; override;
  end;

  { TImgView32 wrapper }

  TPyDelphiImgView32 = class(TPyDelphiControl)
  private
    FBitmap: Vcl.Graphics.TBitmap;
  private
    function GetDelphiObject: TImgView32;
    procedure SetDelphiObject(const Value: TImgView32);
  public
    class function DelphiObjectClass: TClass; override;
    property DelphiObject: TImgView32 read GetDelphiObject write SetDelphiObject;
  public
    constructor Create(APythonType: TPythonType); override;
    constructor CreateWith(PythonType: TPythonType; args: PPyObject); override;
    destructor Destroy; override;
  end;

implementation

uses
  WrapVclExtCtrls;

type
  TByteDynArray = array of Byte;

{ TImgMouseEventHandler }

constructor TImgMouseEventHandler.Create(PyDelphiWrapper : TPyDelphiWrapper; Component : TObject; PropertyInfo : PPropInfo; Callable : PPyObject);
var
  Method : TMethod;
begin
  inherited;

  Method.Code := @TImgMouseEventHandler.DoEvent;
  Method.Data := Self;
  SetMethodProp(Component, PropertyInfo, Method);
end;

procedure TImgMouseEventHandler.DoEvent(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X: Integer; Y: Integer; Layer: TCustomLayer);
var
  PyObject, PyTuple, PyButton, PyX, PyY, PyLayer, PyResult: PPyObject;
begin
  Assert(Assigned(PyDelphiWrapper));
  if Assigned(Callable) and PythonOK then
    with GetPythonEngine do begin
      PyObject := PyDelphiWrapper.Wrap(Sender);
      PyButton := PyLong_FromLong(Ord(Button));
      PyX := PyLong_FromLong(X);
      PyY := PyLong_FromLong(Y);
      PyLayer := Py_None; //### TAKE CARE: Layers are not supported yet!!!
      PyTuple := PyTuple_New(6);
      GetPythonEngine.PyTuple_SetItem(PyTuple, 0, PyObject);
      GetPythonEngine.PyTuple_SetItem(PyTuple, 1, PyButton);
      GetPythonEngine.PyTuple_SetItem(PyTuple, 2, ShiftToPython(Shift));
      GetPythonEngine.PyTuple_SetItem(PyTuple, 3, PyX);
      GetPythonEngine.PyTuple_SetItem(PyTuple, 4, PyY);
      GetPythonEngine.PyTuple_SetItem(PyTuple, 5, PyLayer);
      try
        PyResult := PyObject_CallObject(Callable, PyTuple);
        if Assigned(PyResult) then
        begin
          Py_DECREF(PyResult);
        end;
      finally
        Py_DECREF(PyTuple);
      end;
      CheckError;
    end;
end;

class function TImgMouseEventHandler.GetTypeInfo : PTypeInfo;
begin
  Result := System.TypeInfo(TImgMouseEvent);
end;

{ TPyDelphiImgView32 }

class function TPyDelphiImgView32.DelphiObjectClass: TClass;
begin
  Result := TImgView32;
end;

constructor TPyDelphiImgView32.Create(APythonType: TPythonType);
begin
  inherited;

  FBitmap := Vcl.Graphics.TBitmap.Create;
  FBitmap.Width := 100;
end;

constructor TPyDelphiImgView32.CreateWith(PythonType: TPythonType; args: PPyObject);
begin
  inherited;
end;

destructor TPyDelphiImgView32.Destroy;
begin
  if FBitmap <> nil then
    FreeAndNil(FBitmap);

  inherited;
end;

function TPyDelphiImgView32.GetDelphiObject: TImgView32;
begin
  Result := TImgView32(inherited DelphiObject);
end;

procedure TPyDelphiImgView32.SetDelphiObject(const Value: TImgView32);
begin
  inherited DelphiObject := Value;
end;

{***
 *  TAKE CARE
 *  =========
 *  At moment I've prefered to create the extra python function update_imgview32_from_bytes which call internal wrapped
 *  UpdateImg32ViewFromBytesArray_Wrapper instead to add it directly to TPyDelphiImgView32. The second possibility is
 *  create a local version of TImgView32 -> TImageView32Ex in which add the UpdateImage() function and leave to P4D to
 *  create a wrapped interface. The bad thing of this way is that TByteDynArray is a tkDynArray and P4D converts it
 *  to a list which is managed byte for byte to reconstruct a Delphi dynamic array becoming very very slow.
 *
 **}

function UpdateImg32ViewFromBytesArray_Wrapper(pself, args: PPyObject): PPyObject; cdecl;
{
        A                    B                                                      C
        +-------------------++------------------------------------------------------+
        | PyObject header   ||             TPyObject class                          |
        +----------+--------++-----------------+------------+----------+------------+
        |ob_refcnt |ob_type ||hidden Class Ptr |PythonType  |IsSubType |PythonAlloc |
        |integer   |pointer ||pointer          |TPythonType |Boolean   |Boolean     |
        |4 bytes   |4 bytes ||4 bytes          |4 bytes     |1 byte    |1 byte      |
        +----------+--------++-----------------+------------+----------+------------+

        ^                    ^
        |                    |
        ptr returned         ptr returned by Adjust
        by GetSelf

        - a Python object must start at A.
        - a Delphi class class must start at B
        - TPyObject.InstanceSize will return C-B
        - Sizeof(TPyObject) will return C-B
        - The total memory allocated for a TPyObject instance will be C-A,
          even if its InstanceSize is C-B.
        - When turning a Python object pointer into a Delphi instance pointer, PythonToDelphi
          will offset the pointer from A to B.
        - When turning a Delphi instance into a Python object pointer, GetSelf will offset
          Self from B to A.
        - Properties ob_refcnt and ob_type will call GetSelf to access their data.
}
var
  Width: Integer;
  Height: Integer;
  Channels: Integer;
  Image: TImgView32;
  Bytes: TByteDynArray;
  BytesPyObj: PPyObject;
  ImagePyObj: PPyObject;
  ImageBitmap: Vcl.Graphics.TBitmap;

  function PyBytesAsBytes(Obj: PPyObject): TByteDynArray;
  var
    Size: NativeInt;
    Buffer: PAnsiChar;
  begin
    Result := nil;
    with GetPythonEngine do
    begin
      if not PyBytes_Check(Obj) then Exit;
      PyBytes_AsStringAndSize(Obj, Buffer, Size);
      if Size = 0 then Exit;
      SetLength(Result, Size);
      CopyMemory(Result, Buffer, Size);
    end;
  end;

  function UpdateImg32ViewFromBytesArray(Bytes: TByteDynArray; Width, Height, Channels: Integer): Boolean;

    {***
     *  TAKE CARE
     *  =========
     *  Unfortunately Windows SetDIBitsToDevice requires that Bytes image lines are aligned with DWORD (4 bytes), so
     *  when Width is not a multiple of 4 bytes the result is a tilted image. At moment I don't know a fast way to
     *  align the flow of bytes. A possibility is to create ONLY 4 bytes aligned Width images in Python application but
     *  sincerely I dont like this approach. Other possibility is to try generating RGBA bytes with A fiexd to 255, so
     *  A is unmanaged but this mean more bytes to move.
     *
     *  To solve, at moment I've created an UpdateImg32ViewFromBytesArrayEx which recreate the Bitmap from bytes using
     *  scanline, so a row at time. I hope this is not a too slow way to do.
     *
     **}

    function UpdateBitmapFromBytesArray(Bitmap: Vcl.Graphics.TBitmap; Bytes: TByteDynArray; Width, Height, Channels: Integer): Boolean;
    type
      TRGBBitmapInfoHeader = record
        Header: TBitmapInfoHeader;
        ColorTable: array[0..255] of TRGBQuad;
      end;
      PBitmapInfoHeader = ^TBitmapInfoHeader;
    var
      I: Integer;
      Buffer: TRGBBitmapInfoHeader;
      BmpInfoHeader: PBitmapInfoHeader;
    var
      BmpInfoBuffer: TBitmapInfo absolute Buffer;
    begin
      Result := False;
      try
        if Length(Bytes) = 0 then
          Exit(False);
        if Length(Bytes) <> (Width * Height * Channels) then
          Exit(False);
        if not Channels in [1, 3] then
          Exit(False);
        if Bitmap = nil then
          Exit;

        Bitmap.Width := Width;
        Bitmap.Height := Height;

        BmpInfoHeader := PBitmapInfoHeader(@Buffer);
        BmpInfoHeader^.biSize          := SizeOf(TBitmapInfoHeader);
        BmpInfoHeader^.biWidth         := Width;
        BmpInfoHeader^.biHeight        := -Height;
        BmpInfoHeader^.biPlanes        := 1;
        BmpInfoHeader^.biBitCount      := 8 * Channels;
        BmpInfoHeader^.biCompression   := BI_RGB;
        BmpInfoHeader^.biSizeImage     := 0;
        BmpInfoHeader^.biXPelsPerMeter := 0;
        BmpInfoHeader^.biYPelsPerMeter := 0;
        BmpInfoHeader^.biClrUsed       := 0;
        BmpInfoHeader^.biClrImportant  := 0;

        // if Bytes array is for monochrome image (channels = 1) normalizes bitmap color table
        // https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
        //
        // The BITMAPINFOHEADER structure may be followed by an array of palette entries or color masks.
        // The rules depend on the value of biCompression.
        //
        // - If biCompression equals BI_RGB and the bitmap uses 8 bpp or less, the bitmap has a color table immediately
        //   following the BITMAPINFOHEADER structure. The color table consists of an array of RGBQUAD values. The size
        //   of the array is given by the biClrUsed member. If biClrUsed is zero, the array contains the maximum number
        //   of colors for the given bitdepth; that is, 2^biBitCount colors.
        if Channels = 1 then
        begin
          for I := 0 to 255 do
          begin
            Buffer.ColorTable[I].rgbBlue := I;
            Buffer.ColorTable[I].rgbGreen := I;
            Buffer.ColorTable[I].rgbRed := I;
          end;
        end;

        Result := SetDIBitsToDevice
        (
          Bitmap.Canvas.Handle, // hdc        | handle to device context
          0,                    // xDest      | x-coordinate of upper-left corner of
          0,                    // yDest      | y-coordinate of upper-left corner of
          Width,                // w          | source rectangle width
          Height,               // h          | source rectangle height
          0,                    // xSrc       | x-coordinate of Lower-left corner of
          0,                    // ySrc       | y-coordinate of Lower-left corner of
          0,                    // StartScan  | first scan line in array
          Height,               // cLines     | number of scan lines
          Bytes,                // *lpvBits   | address of array with DIB bits
          BmpInfoBuffer,        // *lpbmi     | address of structure with bitmap info
          DIB_RGB_COLORS        // ColorUse   | RGB or palette indexes
        ) > 0;
      except
      end;
    end;

    function UpdateBitmapFromBytesArrayEx(Bitmap: Vcl.Graphics.TBitmap; Bytes: TByteDynArray; Width, Height, Channels: Integer): Boolean;
    var
      I: Integer;
      Size: Integer;
      SOffset: Integer;
      DOrigin: Pointer;
    begin
      Result := False;
      try
        if Length(Bytes) = 0 then
          Exit;
        if Length(Bytes) <> (Width * Height * Channels) then
          Exit;
        if not Channels in [1, 3] then
          Exit;
        if Bitmap = nil then
          Exit;

        Bitmap.Width := Width;
        Bitmap.Height := Height;
        case Channels of
          1: Bitmap.PixelFormat := pf8bit;
          3: Bitmap.PixelFormat := pf24bit;
        end;

        SOffset := 0;
        Size := Width * Channels;
        for I := 0 to Height - 1 do
        begin
          DOrigin := Bitmap.ScanLine[I];
          CopyMemory(DOrigin, @Bytes[SOffset], Size);
          Inc(SOffset, Size);
        end;

        Result := True;
      except
      end;
    end;

  begin
    Result := UpdateBitmapFromBytesArrayEx(ImageBitmap, Bytes, Width, Height, Channels);
    Image.Bitmap.Assign(ImageBitmap);
  end;

begin
  with GetPythonEngine do
  begin
    try
      if PyArg_ParseTuple(args, 'OOiii:update_imgview32_from_bytes', @ImagePyObj, @BytesPyObj, @Width, @Height, @Channels) <> 0 then
      begin
        if ImagePyObj.ob_type.tp_name <> 'ImgView32' then Abort;
        Image := TPyDelphiImgView32(TPyObject(PAnsiChar(ImagePyObj) + SizeOf(PyObject))).DelphiObject;
        if Image = nil then Abort;

        ImageBitmap := TPyDelphiImgView32(TPyObject(PAnsiChar(ImagePyObj) + SizeOf(PyObject))).FBitmap;
        if ImageBitmap = nil then Abort;

        if BytesPyObj.ob_type.tp_name <> 'bytes' then Abort;
        Bytes := PyBytesAsBytes(BytesPyObj);
        if Bytes = nil then Abort;
        if Length(Bytes) <> (Width * Height * Channels) then Abort;

        if not UpdateImg32ViewFromBytesArray(Bytes, Width, Height, Channels) then Abort;
        Result := ReturnTrue;
      end
      else
        Result := ReturnFalse;
    except
      Result := ReturnFalse;
    end;
  end;
end;

{ register the wrappers, the globals and the constants }

type
  TThirdPartiesCtrlsRegistration = class(TRegisteredUnit)
  public
    function Name : string; override;
    procedure RegisterWrappers(APyDelphiWrapper : TPyDelphiWrapper); override;
    procedure DefineFunctions(APyDelphiWrapper : TPyDelphiWrapper); override;
    procedure DefineVars(APyDelphiWrapper : TPyDelphiWrapper); override;
  end;

{ TThirdPartiesCtrlsRegistration }

procedure TThirdPartiesCtrlsRegistration.DefineFunctions(APyDelphiWrapper: TPyDelphiWrapper);
begin
  inherited;

  APyDelphiWrapper.RegisterFunction
  (
    PAnsiChar('update_imgview32_from_bytes'),
    UpdateImg32ViewFromBytesArray_Wrapper,
    PAnsiChar('update graphics32 ImgView32 object from bytes array with width, height & channels')
  );
end;

procedure TThirdPartiesCtrlsRegistration.DefineVars(APyDelphiWrapper: TPyDelphiWrapper);
begin
  inherited;

  // TScrollBarVisibility enum
  APyDelphiWrapper.DefineVar('svAlways',      svAlways);
  APyDelphiWrapper.DefineVar('svHidden',      svHidden);
  APyDelphiWrapper.DefineVar('svAuto',        svAuto);
end;

function TThirdPartiesCtrlsRegistration.Name: string;
begin
  Result := 'ThirdPartiesGraphics32';
end;

procedure TThirdPartiesCtrlsRegistration.RegisterWrappers(APyDelphiWrapper: TPyDelphiWrapper);
begin
  inherited;

  APyDelphiWrapper.RegisterDelphiWrapper(TPyDelphiImgView32);

  APyDelphiWrapper.EventHandlers.RegisterHandler(TImgMouseEventHandler);
end;

initialization
  RegisteredUnits.Add(TThirdPartiesCtrlsRegistration.Create);
  System.Classes.RegisterClasses([TImgView32]);

end.

 

image.thumb.png.f1a160a89fcd288d1ff266a490c251e3.png

At now frame refresh is very fast and flicker-free.

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×