I want to create a graphical component in Delphi that is editable to some extent
inside the designtime editor.
I would like to know
What component I should inherit from (for example TWinControl or whatever)
How to handle component messages (CM_xxx) to be able to move around my component in the editor
If it's possible to use native windows components in the designtime editor, but then switch to other component in runtime.
The reason I want to be able (if even necessary) to switch to other type of component in runtime is because the component I intend to use is TBitmap32 from the Graphics32 library which is many times faster than standard windows graphics, but TBitmap32 is not inherited from TWinControl to begin with.
Maybe if possible, I could maybe do something like using standard VCL in designtime and then just take its properties and apply them to TBitmap32.
Example:
In designtime I use a TImage which I can move around, and when I run the application it takes the X and Y values, and the bitmap from TImage and apply them to the TBitmap32 component and draw the TBitmap32 component to wherever it needs to be drawn.
Code could look something like this:
TMyBMP= class(TImage)
private
fResultBMP: TBitmap32;
.....
I hope you understand, thank you!
I would not use different components at design-time and run-time. That will just over-complicate your component design. What you use at run-time should be the same thing you use at design-time.
What I would do is have your component contain a TBitmap32 member, override the Paint() method to draw the bitmap at both run-time and design-time, and then respond to the CM_DESIGNHITTEST message so that your component can react to mouse activity at design-time while the mouse is over the bitmap. You can then override the standard MouseDown/Move/Up() methods to manipulate your bitmap positioning/sizing as needed (inside those methods, you can differentiate between run-time and design-time by checking your component's ComponentState property for the csDesigning flag).
To save the bitmap to the DFM, you can either expose the TBitmap32 as a published property (which offers an alternative way of manipulating the bitmap at design-time), or override your component's DefineProperties() method to stream the bitmap manually.
You can create a component inherited from TGraphicControl or TWinControl. The latter is needed if you want your control be able to receive focus and Windows messages.
Your component should use TBitmap32 as a buffer - you do all paint there, in memory. In the Paint method (which you override) you copy contents from the buffer to screen using BitBlt or similar function.
Related
In a Windows Firemonkey application, when I issue the following for a rectangle, in the middle of a form:
rect.Canvas.BeginScene;
rect.Canvas.Clear(TAlphaColorRec.White);
rect.Canvas.EndScene
the entire form is painted white.
Was not the case in XE2. Is the case in Delphi 10.
What's the new paradigm I have overlooked?
The canvas of the primitive (e.g TPaintBox and all TShape descendants) controls, is the canvas of the form. The controls draw directly on this canvas. Therefore a call to Control.Canvas.Clear() clears the whole form. Instead of calling Clear you should use the Fill property of the TRectangle, like so:
Rectangle1.Fill.Color := TAlphaColorRec.White;
I can't comment on how it was in Delphi XE2, but there has been many changes to the FMX library, since then. Maybe this is one of them.
Also refer to FireMonkey Component Rendering
I try to make use of a TImage32 to combine several layers with positions and transparency etc. So I create in runtime a TImage32, set parent to nil, load from file a bitmap and load from file a layer on top of that bitmap. Now I want to save the result, but I seem to be unable to find where the actual result is. If I do the same with creating the TImage32 in designtime, make it visible, the result of the combined bitmaps is in the Buffer field of TImage32, and I can save the result using Image32.Buffer.SaveToFile('test.bmp'). If the component is not visible, the Buffer is an empty bitmap and the combined bitmap seem to be not created.
Can someone shed light on this? How do I combine bitmaps with GR32, save them, but with invisible components?
Thanks a lot!
Willem
You don't need to use visual controls like TImage.
The library you're using graphics32 has all the methods you need.
Use TBitmap32: The Bitmap can be displayed and scaled using its DrawMode, MasterAlpha and StretchFilter properties.
You simply use the MyBitmap.LoadFromFile method to get it.
I suggest you then store your bitmaps in a TObjectList.
Combine them using TBitmap32.Draw{To}, note that you can use the DrawMode to modify the behavior of Draw.
And use the SaveToFile method as usual when done manipulating the bitmap.
Preambule:
I'm working with Black Magic Design (BMD) Decklink input card to acquire HD video signal. They provide C++ Sample with their SDK. I've successfully translated the c++ sample into Delphi (VCL). I've also isolated the API call in a TDecklink witch I want it to be available to the Delphi community. It work very well in VCL (I can provide the TDecklnk with a demo app to use it if requested).
Now I need to acquire the signal in a FMX form (but not crosscompile to other platform than Windows). I've tried to modify the TDecklink to be usable in FMX without success.
Core Question:
In my VCL version, I pass a TPaintBox refference to my TDecklink. The TPaintBox is used by the GraphBuilder as area to display the live video.
Here is some line of code I use in the VCL version to assign the TPaintBox to the GraphBuilder:
pWnd := WindowFromDC(FpboxPreview.Canvas.Handle); //WindowFromDC retreive HWND from HDC
hr:= pIVMRWindowlessCtrl.SetVideoClippingWindow(pWnd); // set the bounds of the video to the preview window
if hr = S_OK then
begin
previewRect.Left := FpboxPreview.Left;
previewRect.Right := FpboxPreview.Width;
previewRect.Top := FpboxPreview.Top;
previewRect.Bottom := FpboxPreview.Height;
hr:= pIVMRWindowlessCtrl.SetVideoPosition(nil, #previewRect); // show the whole of the source frame in the whole of the client area of the control
hr:= pIVMRWindowlessCtrl.SetAspectRatioMode(VMR_ARMODE_LETTER_BOX); // maintain the aspect ratio of the video
hr:= pIVMRWindowlessCtrl.SetBorderColor(GetSysColor(COLOR_BTNFACE)); // set the colour of the letter or pillar boxed area
Where PWnd is a HWND
In FMX, what is the best component and parameter to use to provide what the GraphBuilder expect to receive ?
In VCL, TPaintBox is a TGraphicControl descendant that draws onto the HDC of its Parent control's HWND. When the Parent control receives a WM_PAINT message, it draws itself onto the provided HDC as needed, and then temporarily gives that same HDC to each child TGraphicControl when drawing them, clipping the HDC to each child's coordinates and rectangle accordingly. If you try to draw onto a TGraphicControl.Canvas from outside of its Parent control's WM_PAINT handler (which you should never do), TCanvas will temporarily grab the Parent control's HDC using the Win32 API GetDC() function.
Thus, this statement:
pWnd := WindowFromDC(FpboxPreview.Canvas.Handle);
Is effectively the same as this:
pWnd := FpboxPreview.Parent.Handle;
So, you are actually putting your video on the window of the TPaintBox.Parent control, not on the TPaintBox itself. If you want the video associated with its own control, consider using TPanel instead, as it is a TWinControl descendant with its own HWND.
FireMonkey, on the other hand, has no concept of TGraphicControl and TWinControl. Every control is a TControl descendant with an overridden Paint() method to handle any custom drawing onto a TCanvas that is provided by either the parent TForm or the caller of the TControl.PaintTo() method. FireMonkey does not even create an HWND for each control. Only the parent TForm has its own HWND (so it can interact with the OS). Child controls are drawn directly onto that window, adjusting the drawing coordinates and clipping rectangle accordingly as they go along (under the hood, FireMonkey uses DirectX (Windows) or OpenGL (other platforms) for all of its drawing).
So, if you really need an HWND for your video class to display on, you will have to either:
use the HWND of a TForm, which you can get by either passing its Handle property to the
FMX.Platform.Win.WindowHandleToPlatform() function (or the FMX.Platform.Win.FmxHandleToHWND() function on older FireMonkey versions):
uses
..., FMX.Platform.Win;
pWnd := WindowHandleToPlatform(Form1.Handle);
Or passing the TForm itself to the FMX.Platform.Win.FormToHWND() function:
uses
..., FMX.Platform.Win;
pWnd := FormToHWND(Form1);
use the Win32 API directly to create your own HWND as needed and then embed it inside the HWND of a TForm.
Otherwise, you will have to re-think your video UI in FireMonkey. For instance, assuming the video class can provide you with images of the video frames, you can draw them onto the TPaintBox.Canvas from within the TPaintBox.OnPaint event (which is how TPaintBox is meant to be used in the first place, in both VCL and FireMonkey). Or maybe derive your own custom TControl that pulls images from the video class in its own overridden Paint() method. I don't know what your GraphBuilder class is capable of, but BMD provides an SDK for controlling video recording/playback hardware and accessing video data (see this PDF).
How should I paint text on canvas so that the text won't disappear after repaint/update/refresh without repainting it again and again? Like it was painted as image and not temporarily painted.
If you are talking about, for example, a TPaintBox control or something similar, then there is no persistent canvas to paint on. The system simply is not designed that way and the VCL controls reflect the underlying Windows framework.
The normal approach is as follows:
Paint first to an offscreen bitmap.
When the system asks for a repaint, draw the offscreen bitmap onto the screen canvas.
There are a variety of reasons that may lead you to this approach. Often performance is a factor. It may be expensive to paint and caching the image can help. Sometimes the information required in order to paint may be transient and again caching the output may be a solution.
You can't. Painting only shows the image on the screen once; if you want it to persist then you must repaint it each time the OS requests it.
Use a TLabel (or some derivative) and place it over the canvas. The TLabel will redraw itself whenever necessary.
You need to only draw when the system says you should. There's two things to know about with this subject...
Cache
You can also implement your own cache system. This can get a little tricky when working with many layers. You may have a particular area which is expected to change at a high rate. And then the background presumably isn't going to be changed unless it's been resized, or a color has changed, etc. Such as a needle moving on top of some photo. Just maintain two different image objects in the background and combine them, making sure at least the one(s) on top are transparent.
You can also tell Windows when your control's cache is invalidated (next subject...) by using the Invalidate command. This will tell Windows that something in your control has changed to the point where you need to redraw everything. Windows will then decide when it's ready to actually tell your control to be painted again by calling a Paint procedure.
System Paint
As David mentions in his answer, if you're working with a control, then you should repaint your background when the system says you should. This is accomplished by inheriting the Paint procedure from the TGraphicControl or the TCustomControl (and some others). This procedure is called every time the system says you need to refresh your control's contents. It's the system's way of telling you when your cache is invalidated.
procedure Paint; override;
...
procedure TMyCustomControl.Paint;
begin
DoSomeDrawingOnCanvas;
end;
On the other hand, you can tell Windows whenever you want it to call this Paint procedure too...
procedure TMyCustomControl.SetWidth(const Value: Integer);
begin
if Value <> FWidth then begin //Just a common check for performance reasons
FWidth:= Value;
Invalidate; //This tells Windows that you want to repaint your control
end;
end;
I had this problem when I was developing my own component from this class: TImage
And I used this code:
procedure Paint;override;
begin
inherited
canvas.TextOut(5,5,'Hi')
end;
Thanks in advance
Because the TImage is descendant of TGraphicControl and the procedure Paint is handled inside
WMPaint (windows WM_PAINT) message. So when you paint (inside paint procedure) to the TImage canvas the windows send WM_PAINT message and Paint is called again.
EDIT:
One way to do this is...
procedure TMyImage.Paint;
const
Text = 'Hi';
begin
inherited;
Windows.ExtTextOut(Canvas.Handle, 5, 5, 0, nil, PChar(Text), Length(Text), nil);
end;
Because Windows.ExtTextOut is API call and will not send WM_PAINT message like...
canvas.TextOut(5,5,'Hi')
...which internaly call FreeImage procedure.
When you paint to a TImage, you're really painting to the underlying TPicture object that it displays. If the Picture property isn't yet assigned, then the control will allocate a new TBitmap for itself so you can draw on that instead. But when it assigns its own Picture property, that triggers it to repaint itself.
TImage isn't really designed to draw on itself the way you're doing it. You're going to deface whatever graphic your users assign, which usually isn't what people expect from a TImage. If you want to draw an overlay for the TImage without modifying the contained graphic, then you should draw to inherited Canvas instead of just Canvas.
Or, if you don't really want to display a user's graphic and just want something to paint on, then plain old TGraphicControl is probably the better base class. Or you might not even need a custom control at all. Just put a TPaintBox on your form and put your drawing commands in its OnPaint event.
You should select TGraphicControl (or TCustomControl) as the parent of your own component to override Paint method properly.