Title: How do I take a screenshot and break it into smaller pieces?
This is rather a component which I've built as a piece of a much larger remote desktop application. This component - TJDRMDesktop - has a simple task: Provide smaller "blocks" of a screenshot of the entire desktop. This is just the unit, you can attach this to any package and install it. It contains all the necessary functionality to take a screenshot, break it down into smaller pieces, and piece it back together painlessly. The point is to save the amount of bandwidth by only streaming the smaller pieces of the screen which have changed since the last time it was sent. I've also provided comments in the code to understand what's going on.
Now let me explain how it works.
TJDRMDesktop has a few properties:
- PixelFormat - what format the bitmap should be
- VerticalCount: How many rows of sections to split screen into
- HorizontalCount: How many columns of sections to split screen into
(Vertical and Horizontal count properties best recommended around 10 each, so screen is cut into 100 blocks)
(Larger numbers will slow it down, smaller numbers will defeat the purpose)
- Active - whether component should be taking screenshots or not
(False means nothing happens)
- Bitmap - provides the latest screenshot
- LastBitmap - provides the last bitmap prior to the current Bitmap
- OnNewBlock - Event triggered when a new section or 'block' of the screen is available
(not triggered when a block has not changed since the last time it was sent)
Now the component has a timer which is only processed when active property is set to True. When the timer triggers, it loops through both the Vertical and Horizontal section count of the screen's 'blocks'. It then compares each of those blocks to the corresponding block of the previous screenshot. If that block is any different than the last one, it then triggers the new block event (and replaces the 'last' bitmap's block with the new one).
What does this do? It ensures that you're only getting sections of the screen which have changed, and not getting sections which have remained untouched. When working with remote desktop applications, this is very important to clean up the bandwidth used. Windows remote desktop works similar to this, only sending little chunks of the screen at a time.
You can create another TJDRMImageSplitter object somewhere on the other end to replace that section with the new one. Use TJDRMImageSplitter.SetBlock(Value: TJDRMImageBlock) to replace that corresponding section with the new one. TJDRMImageBlock is inherited from a TBitmap (so is the TJDRMImageSplitter) but contains also the top and left positions of the screen which it needs to be placed on the other end.
Now this is still a rather unfinished version of what I will wind up with, but it is fully functional. I plan on adding more settings such as delays and jpeg compression, but that will come in when I go to plug this component into the rest of my RDP system. I know this component may be a little sluggish, but that's where I'm hoping someone can pick this up and fix it eventually.
Please let me know if and when you want an updated version. I plan to have a better more complete version within a few weeks. I'm also working on converting (and compressing) a bitmap to a jpeg (simple, but at the same time tricky) and then to a ready-to-send memory stream. I'm also working on other components for the other end of the spectrum which receive the stream and put the pieces back together.
However, it will probably be a couple more months before I have a working version of the actual final system :(
I'm building this thing from the ground up, considering every possible detail and making it flexible and expandable from the beginning. In the end, I will have a 3 sided system (Server, Client, and Dashboard) for a full desktop surveillance system. I'm still pondering the idea of giving the entire source up for grabs... but here's at least the starting point of my project.
(by the way, I've already had complete working RDP systems, it's just a matter of restarting from scratch in a much more flexible fashion for a more powerful final product. My current project already has around 30,000 lines of code and an complex SQL database, it's nothing little)
Enjoy......................................
(Updated 11/15/10 7:20 PM)
(Will be providing more detailed documentation soon)
CODE
unit JDRMGraphics;
//Built in Delphi 7
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, JPeg, StrUtils, ExtCtrls,
Forms, Controls;
type
TJDRMImageBlock = class;
TJDRMImageSplitter = class;
TJDRMCustomDesktop = class;
TJDRMDesktop = class;
TJDRMCustomDesktopView = class;
TJDRMDesktopView = class;
//TJDRMImageBlock
//Represents a smaller portion of the larger screenshot
TJDRMImageBlock = class(TComponent)
private
fBitmap: TBitmap; //Main instance of a bitmap (default)
fLeft: Integer; //Left (X) position of section on larger screenshot
fTop: Integer; //Top (Y) position of section on larger screenshot
fCompression: Integer;
fScreenWidth: Integer;
fScreenHeight: Integer;
procedure SetWidth(Value: Integer);
procedure SetHeight(Value: Integer);
procedure SetPixelFormat(Value: TPixelFormat);
function GetWidth: Integer;
function GetHeight: Integer;
function GetPixelFormat: TPixelFormat;
function GetJpeg: TJPEGImage;
function GetStream: TMemoryStream;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property AsBitmap: TBitmap read fBitmap;
property AsJpeg: TJPEGImage read GetJpeg;
property AsStream: TMemoryStream read GetStream;
published
property Left: Integer read fLeft write fLeft;
property Top: Integer read fTop write fTop;
property Width: Integer read GetWidth write SetWidth;
property Height: Integer read GetHeight write SetHeight;
property ScreenWidth: Integer read fScreenWidth write fScreenWidth;
property ScreenHeight: Integer read fScreenHeight write fScreenHeight;
property PixelFormat: TPixelFormat read GetPixelFormat write SetPixelFormat;
property Compression: Integer read fCompression write fCompression;
end;
//TJDRMImageSplitter
//Represents the an entire screenshot image, and gives functionality
//to split screenshot into smaller pieces
TJDRMImageSplitter = class(TComponent)
private
fBitmap: TBitmap; //Main instance of image
fHCount: Integer; //Number of blocks horizontally
fVCount: Integer; //Number of blocks vertically
fBitWidth: Integer; //Width of a block - used for performance
fBitHeight: Integer; //Height of a block - used for performance
fCompression: Integer;//Compression of JPEG image
procedure SetVCount(Value: Integer);
procedure SetHCount(Value: Integer);
procedure SetWidth(Value: Integer);
procedure SetHeight(Value: Integer);
procedure SetPixelFormat(Value: TPixelFormat);
function GetWidth: Integer;
function GetHeight: Integer;
function GetPixelFormat: TPixelFormat;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function GetBlock(X, Y: Integer): TJDRMImageBlock; //Returns a specified block
procedure SetBlock(Value: TJDRMImageBlock); //Assigns a specified block
property Bitmap: TBitmap read fBitmap;
published
property VertCount: Integer read fVCount write SetVCount;
property HorzCount: Integer read fHCount write SetHCount;
property Width: Integer read GetWidth write SetWidth;
property Height: Integer read GetHeight write SetHeight;
property PixelFormat: TPixelFormat read GetPixelFormat write SetPixelFormat;
property Compression: Integer read fCompression write fCompression;
end;
//TJDRMCustomDesktop
//Component which contains all necessary functionality for taking a
// screenshot and triggering events when new sections of the screen are available
//Triggered when a new section of screen is available
TJDRMImageBlockEvent = procedure(Sender: TObject; Block: TJDRMImageBlock) of object;
TJDRMCustomDesktop = class(TComponent)
private
fActive: Boolean; //Whether component is active or not
fTimer: TTimer; //Loops through sections of screenshot
fBitmap: TJDRMImageSplitter; //Represents current screenshot image
fLastBitmap: TJDRMImageSplitter; //Represents previous screenshot image
fBusy: Boolean; //Whether component is busy or not
fDrawCursor: Boolean; //Whether or not to draw cursor on bitmap
fCompression: Integer; //JPEG Compression Value (1 - 100)
fNewBlockEvent: TJDRMImageBlockEvent; //Event - triggered when new block is available
procedure SetActive(Value: Boolean); //Sets component active or inactive
procedure SetVerticalCount(Value: Integer); //Sets vertical count
procedure SetHorizontalCount(Value: Integer); //Sets horizontal count
procedure SetPixelFormat(Value: TPixelFormat); //Sets BMP pixel format
procedure SetCompression(Value: Integer); //Sets JPEG Compression
procedure SetDelay(Value: Integer);
function GetVerticalCount: Integer; //Returns vertical count
function GetHorizontalCount: Integer; //Returns horizontal count
function GetPixelFormat: TPixelFormat; //Returns pixel format
function GetDelay: Integer;
procedure GetScreenshot; //Acquires new screenshot
procedure NewBlock(Block: TJDRMImageBlock); //Triggers fNewBlockEvent
procedure TimerOnTimer(Sender: TObject); //Assigned to Timer.OnTimer
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Compression: Integer read fCompression write SetCompression;
property Active: Boolean read fActive write SetActive;
property VerticalCount: Integer read GetVerticalCount write SetVerticalCount;
property HorizontalCount: Integer read GetHorizontalCount write SetHorizontalCount;
property PixelFormat: TPixelFormat read GetPixelFormat write SetPixelFormat;
property DrawCursor: Boolean read fDrawCursor write fDrawCursor;
property Delay: Integer read GetDelay write SetDelay;
property OnNewBlock: TJDRMImageBlockEvent read fNewBlockEvent write fNewBlockEvent;
end;
//Final component representing entire screenshot process
TJDRMDesktop = class(TJDRMCustomDesktop)
published
property Compression;
property Active;
property VerticalCount;
property HorizontalCount;
property PixelFormat;
property DrawCursor;
property OnNewBlock;
end;
//TJDRMCustomDesktopView
//Visual component based on TScrollBox
//Contains all necessary functionality to display desktop from
//image blocks (from TJDRMDesktop) as well as recognizing mouse
//and keyboard events for remote control
TJDRMCustomDesktopView = class(TScrollingWinControl)
private
fBorderStyle: TBorderStyle;
fSplitter: TJDRMImageSplitter;
fImage: TImage;
fTimer: TTimer;
procedure SetImageWidth(Value: Integer);
procedure SetImageHeight(Value: Integer);
procedure SetRefreshRate(Value: Integer);
function GetImageWidth: Integer;
function GetImageHeight: Integer;
function GetRefreshRate: Integer;
procedure TimerOnTimer(Sender: TObject); //Assigned to fTimer.OnTimer
//From original TScrollBox component
procedure SetBorderStyle(Value: TBorderStyle);
procedure WMNCHitTest(var Message: TMessage); message WM_NCHITTEST;
procedure CMCtl3DChanged(var Message: TMessage); message CM_CTL3DCHANGED;
protected
//From original TScrollBox component
procedure CreateParams(var Params: TCreateParams); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure DrawBlock(Block: TJDRMImageBlock);
procedure UpdateImage;
property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle default bsSingle;
property ImageWidth: Integer read GetImageWidth write SetImageWidth;
property ImageHeight: Integer read GetImageHeight write SetImageHeight;
property RefreshRate: Integer read GetRefreshRate write SetRefreshRate;
end;
TJDRMDesktopView = class(TJDRMCustomDesktopView)
published
property RefreshRate;
//From original TScrollBox component
property Align;
property Anchors;
property AutoScroll;
property BevelEdges;
property BevelInner;
property BevelOuter;
property BevelKind;
property BevelWidth;
property BorderStyle;
property Constraints;
property Enabled;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property Visible;
end;
//Misc
function BitmapsAreSame(Bitmap1, Bitmap2: TJDRMImageBlock): Boolean;
function GetPixelSize(Format: TPixelFormat): Integer;
function ScreenShot(DrawCursor: Boolean; Quality: TPixelFormat): TBitmap;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('JD Remote Desktop', [TJDRMDesktop, TJDRMDesktopView]);
end;
//Return proper byte size for bitmap comparison input
function GetPixelSize(Format: TPixelFormat): Integer;
begin
case Format of
pf8bit: Result:= 1;
pf16bit: Result:= 2;
pf24bit: Result:= 3;
pf32bit: Result:= 4;
else
Result:= 0;
end;
end;
//Compares two bitmaps together to see if they're the same or not
function BitmapsAreSame(Bitmap1, Bitmap2: TJDRMImageBlock): Boolean;
var
Ptr1, Ptr2: pointer;
X: integer;
PixelSize: byte;
begin
//By default, assume images are different
Result:= False;
//First check if width, height, or pixel format are the same
if (Bitmap1.Width = Bitmap2.Width) and
(Bitmap1.Height = Bitmap2.Height) and
(Bitmap1.PixelFormat = Bitmap2.PixelFormat) then
begin
//Obtain byte size of pixels based on pixel format
PixelSize:= GetPixelSize(Bitmap1.PixelFormat);
//Loop through height of bitmap and go row by row
for X:= 0 to (Bitmap1.Height-1) do
begin
//Obtain rows of pixels from each bitmap
Ptr1:= Bitmap1.AsBitmap.ScanLine[X];
Ptr2:= Bitmap2.AsBitmap.ScanLine[X];
//Compare bitmap rows together
Result:= CompareMem(Ptr1, Ptr2, Bitmap1.Width * PixelSize);
//If rows are different, then exit loop
if Result = False then Break;
end;
end;
end;
function ScreenShot(DrawCursor: Boolean; Quality: TPixelFormat): TBitmap;
var
DC: HDC;
R: TRect;
CursorInfo: TCursorInfo;
Icon: TIcon;
IconInfo: TIconInfo;
begin
//Create bitmap result
Result:= TBitmap.Create;
//Get desktop handle
DC:= GetDC(GetDesktopWindow);
try
//Set result to new screenshot image
Result.Width:= GetDeviceCaps (DC, HORZRES);
Result.Height:= GetDeviceCaps (DC, VERTRES);
Result.PixelFormat:= Quality;
//Actual acquiring of screenshot image
BitBlt(Result.Canvas.Handle,
0,
0,
Result.Width,
Result.Height,
DC,
0,
0,
SRCCOPY);
finally
ReleaseDC(GetDesktopWindow, DC);
end;
//Draw cursor
if DrawCursor then begin
R:= Result.Canvas.ClipRect;
Icon:= TIcon.Create;
try
CursorInfo.cbSize:= SizeOf(CursorInfo);
if GetCursorInfo(CursorInfo) then
if CursorInfo.Flags = CURSOR_SHOWING then
begin
Icon.Handle:= CopyIcon(CursorInfo.hCursor);
if GetIconInfo(Icon.Handle, IconInfo) then
begin
//Draw cursor image on screenshot image
Result.Canvas.Draw(
CursorInfo.ptScreenPos.x - Integer(IconInfo.xHotspot) - r.Left,
CursorInfo.ptScreenPos.y - Integer(IconInfo.yHotspot) - r.Top,
Icon);
end;
end;
finally
Icon.Free;
end;
end;
end;
// ---------------------- TJDRMImageBlock ------------------------
constructor TJDRMImageBlock.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Self.fBitmap:= TBitmap.Create;
Self.fLeft:= 0;
Self.fTop:= 0;
end;
destructor TJDRMImageBlock.Destroy;
begin
if assigned(Self.fBitmap) then Self.fBitmap.Free;
inherited Destroy;
end;
procedure TJDRMImageBlock.SetWidth(Value: Integer);
begin
Self.fBitmap.Width:= Value;
end;
procedure TJDRMImageBlock.SetHeight(Value: Integer);
begin
Self.fBitmap.Height:= Value;
end;
procedure TJDRMImageBlock.SetPixelFormat(Value: TPixelFormat);
begin
Self.fBitmap.PixelFormat:= Value;
end;
function TJDRMImageBlock.GetWidth: Integer;
begin
Result:= Self.fBitmap.Width;
end;
function TJDRMImageBlock.GetHeight: Integer;
begin
Result:= Self.fBitmap.Height;
end;
function TJDRMImageBlock.GetPixelFormat: TPixelFormat;
begin
Result:= Self.fBitmap.PixelFormat;
end;
function TJDRMImageBlock.GetJpeg: TJPEGImage;
begin
Result:= TJPEGImage.Create;
Result.CompressionQuality:= Self.fCompression;
Result.Assign(Self.fBitmap);
end;
function TJDRMImageBlock.GetStream: TMemoryStream;
var
J: TJPEGImage;
begin
J:= Self.GetJpeg;
try
Result:= TMemoryStream.Create;
J.SaveToStream(Result);
//Add more data to stream for image location, screen size, etc.
finally
J.Free;
end;
end;
// ----------------------- TJDRMImageSplitter -----------------------------
constructor TJDRMImageSplitter.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Self.fBitmap:= TBitmap.Create;
Self.PixelFormat:= pf24bit;
Self.Compression:= 100;
end;
destructor TJDRMImageSplitter.Destroy;
begin
if assigned(Self.fBitmap) then Self.fBitmap.Free;
inherited Destroy;
end;
procedure TJDRMImageSplitter.SetWidth(Value: Integer);
begin
Self.fBitmap.Width:= Value;
end;
procedure TJDRMImageSplitter.SetHeight(Value: Integer);
begin
Self.fBitmap.Height:= Value;
end;
procedure TJDRMImageSplitter.SetPixelFormat(Value: TPixelFormat);
begin
Self.fBitmap.PixelFormat:= Value;
end;
function TJDRMImageSplitter.GetWidth: Integer;
begin
Result:= Self.fBitmap.Width;
end;
function TJDRMImageSplitter.GetHeight: Integer;
begin
Result:= Self.fBitmap.Height;
end;
function TJDRMImageSplitter.GetPixelFormat: TPixelFormat;
begin
Result:= Self.fBitmap.PixelFormat;
end;
//Obtains a specified section of the original full image
function TJDRMImageSplitter.GetBlock(X, Y: Integer): TJDRMImageBlock;
begin
//X = column, Y = row
//Make sure X and Y are within range
if (X = 0) and (X = 0) and (Y Self.PixelFormat then
Result.PixelFormat:= Self.PixelFormat;
Result.Left:= fBitWidth * X;
Result.Top:= fBitHeight * Y;
//Set screen size
Result.ScreenWidth:= Screen.Width;
Result.ScreenHeight:= Screen.Height;
//Set Compression for JPEG
Result.Compression:= Self.Compression;
//Copy section of the screenshot to new canvas
Result.AsBitmap.Canvas.CopyRect(
Rect(
0,
0,
fBitWidth,
fBitHeight
),
Self.Bitmap.Canvas,
Rect(
fBitWidth * X,
fBitHeight * Y,
Self.Width - (fBitWidth * (Self.fHCount - X - 1)),
Self.Height - (fBitHeight * (Self.fVCount - Y - 1))
)
);
except
on e: exception do begin
raise exception.Create('Error during block acquisition: '+#10+
e.Message);
end;
end;
end else begin
raise exception.Create('Block index out of bounds ['+IntToStr(X)+','+IntToStr(Y)+']');
end;
end;
//Draw new block image on canvas
procedure TJDRMImageSplitter.SetBlock(Value: TJDRMImageBlock);
begin
if assigned(Value) then begin
if assigned(Value.fBitmap) then begin
if Value.fScreenWidth Self.fBitmap.Width then
Self.fBitmap.Width:= Value.fScreenWidth;
if Value.fScreenHeight Self.fBitmap.Height then
Self.fBitmap.Height:= Value.fScreenHeight;
Self.Bitmap.Canvas.Draw(Value.Left, Value.Top, Value.fBitmap);
end;
end;
end;
procedure TJDRMImageSplitter.SetVCount(Value: Integer);
begin
//Property Setting Procedure
Self.fVCount:= Value;
Self.fBitHeight:= Round(Self.Height / Self.fVCount);
end;
procedure TJDRMImageSplitter.SetHCount(Value: Integer);
begin
//Property Setting Procedure
Self.fHCount:= Value;
Self.fBitWidth:= Round(Self.Width / Self.fHCount);
end;
// --------------------------- TJDRMCustomDesktop -----------------------------
constructor TJDRMCustomDesktop.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Self.fBusy:= True;
try
Self.fActive:= False;
Self.fBitmap:= TJDRMImageSplitter.Create(Self);
Self.fBitmap.Width:= Screen.Width;
Self.fBitmap.Height:= Screen.Height;
Self.fLastBitmap:= TJDRMImageSplitter.Create(Self);
Self.fLastBitmap.Width:= Self.fBitmap.Width;
Self.fLastBitmap.Height:= Self.fBitmap.Height;
Self.fTimer:= TTimer.Create(Self);
Self.fTimer.Enabled:= False;
Self.fTimer.Interval:= 5;
Self.fTimer.OnTimer:= Self.TimerOnTimer;
//Set Defaults
Self.PixelFormat:= pf32bit;
Self.VerticalCount:= 15;
Self.HorizontalCount:= 15;
Self.Compression:= 100;
finally
Self.fBusy:= False;
end;
end;
destructor TJDRMCustomDesktop.Destroy;
begin
Self.fBusy:= True;
if assigned(fBitmap) then fBitmap.Free;
if assigned(fLastBitmap) then fLastBitmap.Free;
if assigned(fTimer) then fTimer.Free;
inherited Destroy;
end;
//Acquire next screenshot and process it
procedure TJDRMCustomDesktop.TimerOnTimer(Sender: TObject);
var
X, Y: Integer;
B, B2: TJDRMImageBlock;
begin
//Only process if component is not busy
if not Self.fBusy then begin
Self.fBusy:= True;
try
if Self.fActive then begin
//Acquire new screenshot and assign to fBitmap
Self.GetScreenshot;
//Loop through horizontal section count, then vertical
for X:= 0 to Self.fBitmap.HorzCount - 1 do begin
for Y:= 0 to Self.fBitmap.VertCount - 1 do begin
//Exit if not active
if not Self.fActive then begin
Self.fBusy:= False;
Exit;
end;
//Acquire blocks to compare
B:= Self.fBitmap.GetBlock(X, Y);
B2:= Self.fLastBitmap.GetBlock(X, Y);
try
//Compare blocks
if not BitmapsAreSame(B, B2) then
begin
//Trigger new block event
Self.NewBlock(B);
//Set the 'last' bitmap's corresponding block to new one
Self.fLastBitmap.SetBlock(B);
end;
finally
B.Free;
B2.Free;
end;
end;
end;
end;
finally
Self.fBusy:= False;
end;
end;
end;
procedure TJDRMCustomDesktop.SetActive(Value: Boolean);
begin
//Property Setting Procedure
Self.fActive:= Value;
Self.fTimer.Enabled:= Value;
end;
procedure TJDRMCustomDesktop.SetVerticalCount(Value: Integer);
begin
//Property Setting Procedure
Self.fBitmap.VertCount:= Value;
Self.fLastBitmap.VertCount:= Value;
end;
procedure TJDRMCustomDesktop.SetHorizontalCount(Value: Integer);
begin
//Property Setting Procedure
Self.fBitmap.HorzCount:= Value;
Self.fLastBitmap.HorzCount:= Value;
end;
procedure TJDRMCustomDesktop.SetPixelFormat(Value: TPixelFormat);
begin
//Property Setting Procedure
Self.fBitmap.PixelFormat:= Value;
Self.fLastBitmap.PixelFormat:= Value;
end;
procedure TJDRMCustomDesktop.SetCompression(Value: Integer);
begin
//Property Setting Procedure
if Value 100 then Value:= 100;
if Value Self.fBitmap.Width then
Self.fLastBitmap.Width:= Self.fBitmap.Width;
if Self.fLastBitmap.Height Self.fBitmap.Height then
Self.fLastBitmap.Height:= Self.fBitmap.Height;
finally
B.Free;
end;
end;
procedure TJDRMCustomDesktop.NewBlock(Block: TJDRMImageBlock);
begin
//Trigger new block event
if assigned(Self.fNewBlockEvent) then
begin
Self.fNewBlockEvent(Self, Block);
end;
end;
constructor TJDRMCustomDesktopView.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := [csAcceptsControls, csCaptureMouse, csClickEvents,
csSetCaption, csDoubleClicks];
Width := 185;
Height := 41;
Self.VertScrollBar.Visible:= True;
Self.HorzScrollBar.Visible:= True;
Self.fImage:= TImage.Create(Self);
Self.fImage.Parent:= TWinControl(Self);
Self.fImage.Left:= 0;
Self.fImage.Top:= 0;
Self.fImage.AutoSize:= True;
Self.fSplitter:= TJDRMImageSplitter.Create(Self);
Self.fTimer:= TTimer.Create(Self);
Self.fTimer.Interval:= 0;
Self.fTimer.OnTimer:= Self.TimerOnTimer;
Self.fTimer.Enabled:= True;
end;
destructor TJDRMCustomDesktopView.Destroy;
begin
if assigned(Self.fSplitter) then Self.fSplitter.Free;
if assigned(Self.fImage) then Self.fImage.Free;
inherited Destroy;
end;
procedure TJDRMCustomDesktopView.CreateParams(var Params: TCreateParams);
const
BorderStyles: array[TBorderStyle] of DWORD = (0, WS_BORDER);
begin
inherited CreateParams(Params);
with Params do
begin
Style := Style or BorderStyles[FBorderStyle];
if NewStyleControls and Ctl3D and (FBorderStyle = bsSingle) then
begin
Style := Style and not WS_BORDER;
ExStyle := ExStyle or WS_EX_CLIENTEDGE;
end;
end;
end;
procedure TJDRMCustomDesktopView.SetBorderStyle(Value: TBorderStyle);
begin
if Value FBorderStyle then
begin
FBorderStyle := Value;
RecreateWnd;
end;
end;
procedure TJDRMCustomDesktopView.WMNCHitTest(var Message: TMessage);
begin
DefaultHandler(Message);
end;
procedure TJDRMCustomDesktopView.CMCtl3DChanged(var Message: TMessage);
begin
if NewStyleControls and (FBorderStyle = bsSingle) then RecreateWnd;
inherited;
end;
procedure TJDRMCustomDesktopView.SetImageWidth(Value: Integer);
begin
Self.fSplitter.Width:= Value;
Self.fImage.Picture.Assign(Self.fSplitter.Bitmap);
end;
procedure TJDRMCustomDesktopView.SetImageHeight(Value: Integer);
begin
Self.fSplitter.Height:= Value;
Self.fImage.Picture.Assign(Self.fSplitter.Bitmap);
end;
procedure TJDRMCustomDesktopView.SetRefreshRate(Value: Integer);
begin
Self.fTimer.Interval:= Value;
end;
function TJDRMCustomDesktopView.GetImageWidth: Integer;
begin
Result:= Self.fSplitter.Width;
end;
function TJDRMCustomDesktopView.GetImageHeight: Integer;
begin
Result:= Self.fSplitter.Height;
end;
function TJDRMCustomDesktopView.GetRefreshRate: Integer;
begin
Result:= Self.fTimer.Interval;
end;
procedure TJDRMCustomDesktopView.DrawBlock(Block: TJDRMImageBlock);
begin
Self.fSplitter.SetBlock(Block);
end;
procedure TJDRMCustomDesktopView.UpdateImage;
begin
Self.fImage.Picture.Assign(Self.fSplitter.Bitmap);
//Application.ProcessMessages;
end;
procedure TJDRMCustomDesktopView.TimerOnTimer(Sender: TObject);
begin
if Self.fTimer.Interval 0 then begin
Self.UpdateImage;
end;
end;
end.