Rich Edit Text Object Model project

Started by José Roca, January 17, 2025, 06:37:59 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Theo Gottwald

Quote from: José Roca on February 06, 2025, 12:07:24 AM> i hope you also use the new available K.I.-Resourcees, as they wil help you with the boring tasks.You find all important KI-Links here in the Forum.

Sorry. I don't find anything in the forum.



Goto:
IT-Berater: Theo Gottwald (IT-Consultant)
then roll down until you find "Claude 3" which is still the most popular
alternative, or use DeepSeek or one of the other new KI's, just click on the link you will be redirected there.
It can save you from many stupid tasks, so you can concentrate on the oversight.


Theo Gottwald

Quote from: José Roca on February 06, 2025, 12:44:14 AMThere is an curious problem: In my computer, the scaling ratio is 1.75, and InsertImage uses that ratio by default. InsertObject scales the image, but using a ratio of 1.68, and drag and drop scales the image, but using a ratio of
1.24. So you get different image sizes depending of the method that you use, but what can I do?

[quote]Below is an answer that summarizes what I found from online sources regarding inconsistent image scaling ratios in Windows high‐DPI applications:

Why the Different Ratios?
Many developers have noted that when you insert images by different methods (such as through a programmatic InsertImage, via an InsertObject call, or by drag and drop) you may see different scaling ratios. This happens because each method can end up querying the system's DPI settings from different sources or in different contexts. For example:

InsertImage using GDI+ might use the DPI embedded in the image file or the DPI settings obtained via GDI+ APIs.
InsertObject might use the DPI value provided by the Windows common controls or the application's DPI awareness settings.
Drag and Drop often works through a different message loop or DPI virtualization layer and may use a fallback DPI value.
As a result, one method might return 1.75 while another returns 1.68 or 1.24. Essentially, the "scaling ratio" you see depends on the DPI context and API used to obtain it.

How to Resolve It?
Several experts recommend the following unified approach:

Declare Your Application DPI-Aware:
Use an application manifest or call APIs like SetProcessDPIAware (or on Windows 10, SetProcessDpiAwarenessContext) so that your app is fully DPI-aware. This prevents Windows from "virtualizing" DPI values and ensures that all parts of your application use the same DPI context.

Microsoft: High DPI Desktop Application Development on Windows
Centralize DPI Retrieval:
Write a single routine (for example, GetScalingRatio()) that uses a consistent API (such as GetDeviceCaps or GetDpiForWindow) to retrieve the DPI value from your current window or display. Then use that scaling ratio in all your image insertion methods.

Override if Necessary:
If you'd like more control, expose a global property (or a property on your image container) that allows you to override the scaling ratio. For example, you might let the user set a custom ratio (1.0 for no scaling, etc.) so that all insertion methods use exactly that value.

By ensuring your application is fully DPI-aware and by having all methods call the same DPI retrieval function, you guarantee that InsertImage, InsertObject, and drag‐and‐drop will use the same scaling ratio. Several forum posts (for example on MSDN and StackOverflow) support this approach as the most reliable solution to DPI scaling inconsistencies.

In summary:
The differences in scaling (1.75 vs. 1.68 vs. 1.24) come from different DPI contexts. To fix this, make your application DPI‐aware and use one centralized function to get the scaling ratio so that all image‐insertion code paths use the same value.

This solution is in line with best practices from Microsoft's High DPI documentation and many developer forum discussions.[/quote]


[quote]The differences you're seeing are likely due to the fact that each method (InsertImage, InsertObject, drag‐and‐drop) is obtaining the DPI scaling factor from a different source or using a different DPI‐awareness context. In other words, one function might be using the system DPI from GDI+, another might be using values derived from your process or application manifest settings, and drag and drop might be using yet another DPI context.

To resolve this inconsistency, consider the following steps:

Unify DPI Awareness:
Ensure your application is declared as DPI‐aware (using an application manifest or by calling an API like SetProcessDPIAware or SetProcessDpiAwarenessContext). This guarantees that all parts of your application are using the same DPI context when retrieving DPI values.

Use a Common DPI Retrieval Method:
Instead of letting each method calculate its own scaling ratio, call a single routine (for example, a GetScalingRatio function) that uses the same API (for instance, GDI+ or GetDeviceCaps) to retrieve the current DPI and compute the scaling factor. Then pass that ratio explicitly to InsertImage, InsertObject, and any drag and drop handling routines.

Override with a Global Property:
If you wish to allow flexibility, you can introduce a global scaling property (or a property on your image container) that overrides the default computed ratio. That way, you can ensure that all methods use the same ratio (for example, set it to 1.0 for no scaling or to a value you decide on) regardless of the underlying DPI retrieval differences.

Review the DPI-Specific Code:
Check the modifications you made to InsertImage. For example, if you're calling GDI+ functions to get the image's original size and then multiplying by the scaling ratio, verify that the same DPI value is being used by InsertObject and drag and drop routines. You might need to refactor those methods so that they all call your common DPI scaling routine.

For more detailed information on these issues, you might review Microsoft's guidelines on developing high DPI–aware applications (for instance, "High DPI Desktop Application Development on Windows" on Microsoft Docs) and related forum posts on consistent DPI scaling in PowerBASIC applications.

By ensuring that all parts of your application share the same DPI context and by explicitly setting or retrieving the scaling ratio from a single source, you can avoid the discrepancies (1.75 vs. 1.68 vs. 1.24) you're seeing between the various methods.[/quote]
'------------------------------------------------------------------
' 1. DECLARE DPI AWARENESS AND REQUIRED API FUNCTIONS
'------------------------------------------------------------------
#COMPILE EXE
#DIM ALL
#INCLUDE "Win32Api.inc"

' DPI Awareness Context (Windows 10+)
DECLARE FUNCTION SetProcessDpiAwarenessContext LIB "User32.dll" _
    ALIAS "SetProcessDpiAwarenessContext" (BYVAL dpiFlag AS DWORD) AS LONG

DECLARE FUNCTION GetDpiForWindow LIB "User32.dll" _
    ALIAS "GetDpiForWindow" (BYVAL hWnd AS DWORD) AS DWORD

'------------------------------------------------------------------
' 2. INITIALIZE DPI AWARENESS AT PROGRAM START
'------------------------------------------------------------------
FUNCTION InitializeDPIAwareness() AS LONG
    ' Try Windows 10 Per-Monitor v2 awareness
    LOCAL hr AS LONG
    hr = SetProcessDpiAwarenessContext(&HFFFFFFFFFFFFFFF4) ' DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
    IF hr = 0 THEN FUNCTION = 1 ELSE FUNCTION = 0
END FUNCTION

'------------------------------------------------------------------
' 3. GET CONSISTENT SCALING RATIO
'------------------------------------------------------------------
FUNCTION GetSystemScalingRatio(BYVAL hWnd AS DWORD) AS SINGLE
    LOCAL dpi AS DWORD
   
    ' First try GetDpiForWindow (Windows 10+)
    dpi = GetDpiForWindow(hWnd)
   
    ' Fallback to system DPI if failed
    IF dpi = 0 THEN
        LOCAL hDC AS DWORD
        hDC = GetDC(hWnd)
        dpi = GetDeviceCaps(hDC, %LOGPIXELSX)
        ReleaseDC hWnd, hDC
    END IF
   
    FUNCTION = CSng(dpi) / 96.0!
END FUNCTION

'------------------------------------------------------------------
' 4. SAMPLE IMAGE INSERTION WITH CONSISTENT SCALING
'------------------------------------------------------------------
SUB InsertImage(BYVAL hWnd AS DWORD, imagePath AS STRING, OPTIONAL userScaling AS SINGLE)
    ' Default user scaling to 1.0 if not provided
    IF ISMISSING(userScaling) THEN userScaling = 1.0
   
    ' Get combined scaling ratio
    LOCAL sysScaling AS SINGLE
    sysScaling = GetSystemScalingRatio(hWnd)
    LOCAL finalScaling AS SINGLE
    finalScaling = sysScaling * userScaling
   
    ' Get native image dimensions (example using GDI+)
    LOCAL pImage AS IDispatch
    LOCAL nativeWidth AS LONG, nativeHeight AS LONG
    GDIP_LoadImageFromFile(imagePath, pImage)
    GDIP_GetImageDimensions(pImage, nativeWidth, nativeHeight)
   
    ' Apply scaling
    LOCAL scaledWidth AS LONG, scaledHeight AS LONG
    scaledWidth = nativeWidth * finalScaling
    scaledHeight = nativeHeight * finalScaling
   
    ' Insert image with scaled dimensions
    ' (Your actual image insertion code here)
    MSGBOX "Inserting image at:" + STR$(scaledWidth) + "x" + STR$(scaledHeight)
   
    GDIP_DisposeImage(pImage)
END SUB

'------------------------------------------------------------------
' 5. HANDLE DRAG AND DROP CONSISTENTLY
'------------------------------------------------------------------
CALLBACK FUNCTION WindowProc
    SELECT CASE CBMSG
        CASE %WM_DROPFILES
            LOCAL hDrop AS DWORD
            hDrop = CBWPARAM
           
            ' Get scaling ratio for current window
            LOCAL scaling AS SINGLE
            scaling = GetSystemScalingRatio(CBHNDL)
           
            ' Process dropped files
            ' (Your actual drag/drop handling code here)
            MSGBOX "Drag/drop scaling ratio:" + STR$(scaling)
           
            DragFinish hDrop
            FUNCTION = 0
            EXIT FUNCTION
    END SELECT
END FUNCTION

'------------------------------------------------------------------
' MAIN PROGRAM
'------------------------------------------------------------------
FUNCTION PBMAIN()
    InitializeDPIAwareness
   
    ' Create test window
    LOCAL hWnd AS DWORD
    hWnd = CBHNDL
   
    ' Test image insertion
    InsertImage hWnd, "C:\test.png", 1.0  ' No additional user scaling
   
    ' Test with user scaling
    InsertImage hWnd, "C:\test.png", 0.8  ' 80% of system scaling
END FUNCTION






José Roca

#17
My InsertImage function does scaling perfectly (scaling ratio: 1.75). It is the InsertObject function, that uses the IRichEditOle interface, the one that scales using a ratio of 1.64, but it does not provide any method to set the DPI that it has to use, and drag and drop uses an scaling ratio of 1.24. All these numbers happen in my computer and with the images that I'm using; they may change in another system. Both the IRichEditOle interface and Windows drag and drop need a callback that implements several methods, the most important of them being GetNewStorage, but none of them indicates which scaling ratio is going to use and allow to change it. I can control my own code, and use the scaling that I wish, but I have no control to do the same with the other two methods.

José Roca

#18
I have been able to solve the High DPI problem with the InsertObject method, that uses the IRichEditOle interface, by modifying the Microsoft C++ example. The REOBJECT structure has a member that is a SIZEL structure, where you can specify the wdith and height.

Therefore, I have changed this code

   ' // Set up object info
   DIM reobject AS REOBJECT
   reobject.cbStruct = SIZEOF(REOBJECT)
   reobject.clsid = clsid
   reobject.cp = REO_CP_SELECTION
   reobject.dvaspect = DVASPECT_CONTENT
   reobject.dwFlags = REO_RESIZABLE OR REO_BELOWBASELINE
   reobject.dwUser = 0
   reobject.poleobj = pObject
   reobject.polesite = pClientSite
   reobject.pstg = pStorage
   DIM sizel AS SIZEL
   reobject.sizel = sizel

to


   ' // Set up object info
   DIM reobject AS REOBJECT
   reobject.cbStruct = SIZEOF(REOBJECT)
   reobject.clsid = clsid
   reobject.cp = REO_CP_SELECTION
   reobject.dvaspect = DVASPECT_CONTENT
   reobject.dwFlags = 0 'REO_RESIZABLE OR REO_BELOWBASELINE
   reobject.dwUser = 0
   reobject.poleobj = pObject
   reobject.polesite = pClientSite
   reobject.pstg = pStorage
   DIM sizel AS SIZEL
   DIM dwWidth AS DWORD, dwHeight AS DWORD
   DIM nStatus AS LONG = AfxGdipGetImageSizeFromFile(wszFileName, @dwWidth, @dwHeight)
   IF nStatus = 0 THEN
      sizel.cx = AfxPixelsToHiMetricX(dwWidth * m_ScalingRatio)
      sizel.cy = AfxPixelsToHiMetricY(dwHeight * m_ScalingRatio)
   END IF
   reobject.sizel = sizel

I'm removing the REO_RESIZABLE OR REO_BELOWBASELINE flags, getting the width and height of the image using GDI+ and filling the SIZEL structure with the these values multiplied by the scaling ratio, and convert them from pixels to HIMETRIC units.

What remains is the problem with drag and drop.

Theo Gottwald

#19
You can ask:
Tülu 3

QuoteTo address the drag and drop issue with high DPI scaling, you'll need to make adjustments to ensure that the drag and drop operations account for the scaling factor in the same way as you've adjusted the image size. This typically involves translating the mouse coordinates and the dimensions of the dragged object to ensure they are correctly positioned and sized relative to the target window or control.

Here's a general approach to handle drag and drop operations with high DPI scaling:

Capture the Drop Position: When a drop operation occurs, you'll need to capture the position of the drop in screen coordinates.

Translate Coordinates: Convert the screen coordinates to client coordinates relative to the target window or control. This can be done using the ScreenToClient function or a similar method.

Apply DPI Scaling: Adjust the coordinates (and possibly the size) based on the DPI scaling factor. This involves multiplying the coordinates by the scaling ratio to ensure they are correctly positioned within the target window or control.

Insert the Object: With the adjusted coordinates and size, insert the object at the correct position and size within the target window or control.

Here's a simplified example of how you might adjust the drop coordinates:

POINT ptDropScreen; // Assume this is the drop position in screen coordinates
HWND hWndTarget; // Handle to the target window

// Convert screen coordinates to client coordinates
POINT ptDropClient = ptDropScreen;
ScreenToClient(hWndTarget, &ptDropClient);

// Apply DPI scaling
float dpiScalingX = /* Get X scaling factor */;
float dpiScalingY = /* Get Y scaling factor */;
ptDropClient.x = static_cast<LONG>(ptDropClient.x * dpiScalingX);
ptDropClient.y = static_cast<LONG>(ptDropClient.y * dpiScalingY);

// Now use ptDropClient to position the object
To get the DPI scaling factors, you can use the GetDpiForWindow function (available since Windows 10 version 1607) or the GetDeviceCaps function with the LOGPIXELSX and LOGPIXELSY indices.

Remember that the exact implementation will depend on the specifics of your application and the frameworks or libraries you're using. The key is to ensure that all coordinates and sizes are correctly scaled based on the DPI settings of the display.

For more detailed guidance or assistance with specific issues, consider consulting the documentation for the frameworks or libraries you're using, or seek out community resources and forums related to Windows development and high DPI support.

or lets see what

Claude 3.5
says:

QuoteI understand you're dealing with scaling inconsistencies between different image insertion methods in a RichEdit control, specifically:

Your InsertImage function - scales correctly at 1.75x
IRichEditOle InsertObject - scales at 1.64x with no apparent way to control it
Drag and drop - scales at 1.24x, also without control

You've successfully fixed the direct insertion method by:

Removing the REO_RESIZABLE and REO_BELOWBASELINE flags
Getting image dimensions via GDI+
Applying your desired scaling ratio
Converting to HIMETRIC units

For the drag and drop issue, since it's using the OLE subsystem, you might be able to intercept and modify the object before it's inserted. Have you considered implementing the IRichEditOleCallback interface and using its GetNewStorage method to modify the object properties?
Here's a potential approach:
cppCopyclass CRichEditOleCallback : public IRichEditOleCallback
{
    // ... other interface methods ...

    HRESULT GetNewStorage(LPSTORAGE* ppStg)
    {
        // Create storage
        HRESULT hr = CreateStorage(ppStg);
        if (SUCCEEDED(hr))
        {
            // Here you could potentially intercept and modify the
            // object properties before it's inserted
            // You might be able to access the REOBJECT structure
            // and modify its sizel member
        }
        return hr;
    }
};
Would you be able to share more details about how you're currently handling drag and drop? That might help identify where we could intervene to apply the correct scaling.

or you could have asked the famouse

DeepSeek AI

but currently the servers seem to be busy. Too many people use it because its free.

Alternatively you could have asked the good old CHatGPT andd you would get this:
QuoteThe core of the problem is that—even though you can adjust scaling in your own code (as you did with InsertImage and even in your custom InsertObject handler by modifying REOBJECT.sizel)—the drag and drop path is handled entirely by Windows' OLE mechanism. The drag and drop callback (via IRichEditOleCallback, especially methods like GetNewStorage) doesn't provide any parameter or hook to specify a DPI or scaling factor. As a result, Windows calculates and applies its own scaling (in your case, about 1.24) that you can't directly change.

Two potential workarounds are:

• **Intercept and Process Drag Data Manually:** Instead of letting the default drag and drop insertion occur, you could implement a custom drop handler (for example, by subclassing the control's IDropTarget) that intercepts the dropped image data. In that handler, you can manually scale the image (using your desired ratio, such as 1.75 or 1.64) and then insert it into the RichEdit control using your own code (e.g. via a custom InsertObject routine). This gives you full control over the scaling process.

• **Adjust Application DPI Settings:** Ensuring your application is fully DPI-aware (for example, by setting the appropriate `<dpiAwareness>` tag in your manifest) may help align some of the scaling behaviors. However, even with full DPI awareness, Windows' internal drag and drop handling still uses its own logic without exposing a way to override the scaling ratio.

In summary, because neither the IRichEditOle interface nor the drag and drop callback methods expose any DPI or scaling controls, you cannot "tell" Windows to use a different scaling ratio for drag and drop. The only solutions are to either live with the system's default behavior or to implement your own drag and drop mechanism that scales images before insertion.

For reference, the official [IRichEditOleCallback::GetNewStorage documentation](�cite�turn0search0�) shows that while it's critical for operations like cut/copy/paste and drag and drop, it does not offer any DPI or scaling parameters.


The scaling differences you're seeing—in your own code versus when Windows internally handles object insertion (via IRichEditOle) or drag and drop—are not unique to your system. Many developers working with rich edit controls on high‐DPI systems have encountered inconsistencies in how image sizes are converted and rendered. In your case, you observe ratios of 1.75 for your manual InsertImage routine, 1.64 for IRichEditOle–based InsertObject, and 1.24 for drag and drop. Although these numbers may vary by system and image content, they stem from several underlying factors:

---

### 1. DPI Assumptions in Windows and the Rich Edit Control

When you write your own insertion code, you have full control: you retrieve the image dimensions (for example, via GDI+), multiply them by a chosen scaling factor (your "m_ScalingRatio"), and then convert pixels to HIMETRIC units using functions such as AfxPixelsToHiMetricX/Y. This routine works "perfectly" because you are explicitly controlling the conversion.

In contrast, the IRichEditOle interface and the internal drag and drop handlers do not provide any parameter to specify DPI or a custom scaling factor. They use internal logic (often assuming a base DPI of 96) to convert pixel dimensions into HIMETRIC. Because the conversion is performed inside Windows' code paths, you have no hook to adjust the ratio. This explains why the InsertObject and drag and drop code paths yield different scaling ratios—they rely on different internal assumptions and rounding behaviors.

> For example, the [IRichEditOleCallback::GetNewStorage documentation](�cite�turn0search0�) makes it clear that no DPI parameter is provided when storage is allocated for a new object.

---

### 2. Rounding and Internal Conversion Differences

The rich edit control, as well as other parts of Windows' OLE and drag/drop subsystems, often perform conversions between logical units (pixels) and HIMETRIC using integer math. As described in discussions such as the Stack Overflow thread on [rounding errors when scaling the rendered output of the rich edit control via EM_FORMATRANGE](�cite�turn1search3�), these conversions can introduce cumulative rounding errors. In your situation, slight differences in how the width and height are rounded when converting to HIMETRIC may result in scaling ratios like 1.64 versus 1.24.

Each insertion method may trigger a different code path:
- **Your InsertImage routine:** You control the conversion entirely.
- **IRichEditOle InsertObject:** Uses a standard REOBJECT structure and Windows' default conversion (and may also be influenced by flags like REO_RESIZABLE or REO_BELOWBASELINE, which you found it necessary to remove).
- **Drag and drop:** Relies on the drag source's data object and Windows' drag-and-drop helper routines, which again use their own logic for determining size—often optimized for interactive behavior (like cursor display) rather than exact DPI scaling.

Because these internal routines were designed long before high-DPI displays became common, they have hard-coded assumptions (and sometimes "magic numbers") that can result in inconsistent scaling across different operations.

---

### 3. Community Experience and Workarounds

Developers in various forums (including CodeProject and Stack Overflow) have reported similar issues when inserting OLE objects or handling drag and drop on high-DPI systems. The consensus is that:
- **For programmatic insertion (InsertImage):** It is best to compute and set the size manually, as you have done.
- **For OLE-based insertion and drag and drop:** There is no exposed mechanism to override the DPI conversion because the underlying IRichEditOle interface and the drag/drop system do not provide DPI or scaling callbacks.
- **Workarounds:** Some developers choose to post-process the inserted object by manually adjusting its size after insertion. Others implement a custom drag-and-drop mechanism to apply a uniform scaling factor, though this requires reimplementing parts of the drag-and-drop behavior that Windows handles automatically.

This problem is intrinsic to how Windows' rich edit control and OLE embedding were designed. The different ratios you observe (1.75 vs. 1.64 vs. 1.24) are simply manifestations of different default conversion paths and rounding artifacts. Other developers have reported that even with modifications (like yours for InsertObject) the drag-and-drop scenario remains problematic.

---

### In Summary

- **Custom Insertion vs. OLE/Drag-and-Drop:** When you control the conversion (as in your InsertImage function), you can apply your preferred scaling factor. In contrast, IRichEditOle and Windows drag/drop rely on internal conversions with fixed assumptions (often based on 96 DPI), leading to different and less adjustable scaling ratios.
- **Rounding and Code Paths:** Internal rounding and the use of integer math in converting pixels to HIMETRIC can cause small discrepancies that accumulate into the different ratios you've observed.
- **Community Observations:** Similar issues have been noted by others; while some workarounds exist (e.g., manually adjusting the REOBJECT.sizel values or implementing a custom drag-and-drop handler), a uniform solution is hindered by the fact that neither the IRichEditOle interface nor the drag-and-drop callbacks expose DPI settings.

Unfortunately, without a supported API to set the DPI or scaling factor for these operations, you must either accept these differences or implement additional code to adjust the sizes after insertion. This is a well-documented "quirk" among developers dealing with rich edit OLE embedding in high-DPI environments.




Please also test:

Qwen AI
It makes bug-free Powerbasic Code (FB? Don't know ...)
you can even give it the buggy code from other AI's and say "Make it bugfree in Powerbasic".

And he will do it!



José Roca

Now that I have solved the DPI problem with insert object, I think that I have a solution for not displaying the image, but an icon, with other kind of images that are not bitmaps, like .jpg, .png, .gif.

Instead of using OleCreateFromFile to load and render the image, I can use GDI+ to load the image from file with the function GdipCreateBitmapFromFile and get a bitmap handle with GdipCreateHBITMAPFromBitmap. Then I will fill The cfFormat member of the FormatEtc structure with the CF_BITMAP constant and the tymed member with the TYMED_GDI constant. I also will need an instance of the STGMEDIUM structure with its tymed member = TYMED_GDI and its hBitmap member = the bitmap handle returned by GdipCreateHBITMAPFromBitmap. After that, I can call the Windows function OleCreateStaticFromData to create an static object. Looks easy, isn't it? But there is a problem... There is always a problem, isn't it? You can't transfer the data directly to OleCreateStaticFromData. That would be too easy and Microsoft likes to do things in a convoluted way. Why make things easy if you can make them complicated? Instead of providing an API function that accepts the FormatEtc and STGMEDIUM structures, you have to store the data in a DataObject COM object that implements the IDataObject interface and provide a pointer to it to OleCreateStaticFromData, that will use this pointer to access the data.

Regarding drag and drop and the cut and paste operations, they use the clipboard for data transfer.

Theo Gottwald

#21
Once you know what you want, Qwen can code an outline ...

Below is the PowerBASIC code that implements your described solution for loading and rendering images using GDI+ and creating a static object with `OleCreateStaticFromData`. The code includes all necessary declarations, structures, and function calls. It assumes you have already installed the GDI+ library and linked it properly in your project.

### Code Implementation

```basic
#Compile Exe
#Dim All

%Unicode = 1

' Include necessary libraries
#Include "windows.bi"
#Include "ole2.bi"
#Include "gdiplus.bas" ' Ensure this file contains GDI+ declarations

' Declare constants
Global Const CF_BITMAP = 2
Global Const TYMED_GDI = &H3

' Define required structures
Type FORMATETC
    cfFormat As Long
    ptd As Long
    dwAspect As Long
    lindex As Long
    tymed As Long
End Type

Type STGMEDIUM
    tymed As Long
    hBitmap As Long
    pData As Long
End Type

' Function prototypes
Declare Function OleCreateStaticFromData Lib "ole32.dll" (ByVal pDataObject As Long, ByVal riid As Long, ByVal renderopt As Long, ByVal lpFormatEtc As FORMATETC, ByVal pClientSite As Long, ByVal pStg As Long, ByVal ppvObj As Long) As Long
Declare Function CreateStreamOnHGlobal Lib "ole32.dll" (ByVal hGlobal As Long, ByVal fDeleteOnRelease As Long, ppstm As Long) As Long
Declare Function CoTaskMemAlloc Lib "ole32.dll" (ByVal cb As Long) As Long
Declare Function CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long) As Void

' GDI+ initialization
Global gdiplusToken As Dword
GdiplusStartup(gdiplusToken, ByVal VarPtr(GdiplusStartupInput), ByVal 0)

Function CreateDataObject(ByVal hBitmap As Long) As Long
    ' Implement IDataObject interface
    Local pDataObject As Long
    Local iidIDataObject As GUID
    Local formatetc As FORMATETC
    Local stgmedium As STGMEDIUM
    Local hr As Long

    ' Initialize IID_IDataObject
    With iidIDataObject
        .Data1 = &H7FD52380
        .Data2 = &HA1A1
        .Data3 = &H11CE
        .Data4(0) = &H8C
        .Data4(1) = &H7A
        .Data4(2) = &H00
        .Data4(3) = &HAA
        .Data4(4) = &H00
        .Data4(5) = &H4B
        .Data4(6) = &HA7
        .Data4(7) = &HBA
    End With

    ' Allocate memory for IDataObject
    pDataObject = CoTaskMemAlloc(SizeOf(IDataObject))
    If pDataObject = 0 Then Exit Function

    ' Initialize FORMATETC structure
    With formatetc
        .cfFormat = CF_BITMAP
        .ptd = 0
        .dwAspect = DVASPECT_CONTENT
        .lindex = -1
        .tymed = TYMED_GDI
    End With

    ' Initialize STGMEDIUM structure
    With stgmedium
        .tymed = TYMED_GDI
        .hBitmap = hBitmap
        .pData = 0
    End With

    ' Implement SetData method of IDataObject
    ' This is a simplified implementation; you may need to write a full COM object if required.
    ' For simplicity, we assume the data is stored directly in the structure.
    hr = pDataObject.SetData(formatetc, stgmedium, True)
    If hr <> S_OK Then
        CoTaskMemFree(pDataObject)
        pDataObject = 0
    End If

    Function = pDataObject
End Function

Sub Main()
    Local hBitmap As Long
    Local pDataObject As Long
    Local formatetc As FORMATETC
    Local stgmedium As STGMEDIUM
    Local hwndStatic As Long
    Local hr As Long

    ' Load image using GDI+
    Local bitmap As GpBitmap Ptr
    Local status As GpStatus
    status = GdipCreateBitmapFromFile("image.jpg", bitmap)
    If status <> Ok Then
        Print "Failed to load image."
        Exit Sub
    End If

    ' Convert GDI+ bitmap to HBITMAP
    status = GdipCreateHBITMAPFromBitmap(bitmap, hBitmap, 0)
    If status <> Ok Then
        Print "Failed to create HBITMAP."
        GdipDisposeImage(bitmap)
        Exit Sub
    End If

    ' Create IDataObject
    pDataObject = CreateDataObject(hBitmap)
    If pDataObject = 0 Then
        Print "Failed to create IDataObject."
        DeleteObject(hBitmap)
        GdipDisposeImage(bitmap)
        Exit Sub
    End If

    ' Initialize FORMATETC structure
    With formatetc
        .cfFormat = CF_BITMAP
        .ptd = 0
        .dwAspect = DVASPECT_CONTENT
        .lindex = -1
        .tymed = TYMED_GDI
    End With

    ' Create static control
    hwndStatic = CreateWindowEx(0, "STATIC", "", WS_CHILD Or WS_VISIBLE Or SS_BITMAP, 10, 10, 200, 200, GetConsoleWindow(), 0, App.hInstance, 0)
    If hwndStatic = 0 Then
        Print "Failed to create static control."
        CoTaskMemFree(pDataObject)
        DeleteObject(hBitmap)
        GdipDisposeImage(bitmap)
        Exit Sub
    End If

    ' Call OleCreateStaticFromData
    hr = OleCreateStaticFromData(pDataObject, IID_IUnknown, OleRender::OleRenderNormal, formatetc, 0, 0, ByVal 0)
    If hr <> S_OK Then
        Print "Failed to create static object."
    Else
        Print "Static object created successfully."
    End If

    ' Cleanup
    CoTaskMemFree(pDataObject)
    DeleteObject(hBitmap)
    GdipDisposeImage(bitmap)
End Sub

Main()

' Shutdown GDI+
GdiplusShutdown(gdiplusToken)
```

### Explanation of the Code

1. **GDI+ Initialization**: The `GdiplusStartup` function initializes the GDI+ library, which is required to load non-BMP image formats like JPEG, PNG, and GIF.

2. **Loading the Image**: The `GdipCreateBitmapFromFile` function loads the image from a file into a GDI+ bitmap object.

3. **Converting to HBITMAP**: The `GdipCreateHBITMAPFromBitmap` function converts the GDI+ bitmap into an HBITMAP, which is compatible with Windows API functions.

4. **Creating IDataObject**: A custom implementation of the `IDataObject` interface is created to store the HBITMAP. This is required because `OleCreateStaticFromData` expects a pointer to an `IDataObject`.

5. **Creating Static Control**: A static control is created using `CreateWindowEx`, and the `OleCreateStaticFromData` function is called to associate the `IDataObject` with the static control.

6. **Cleanup**: Resources such as the HBITMAP, GDI+ bitmap, and `IDataObject` are released to prevent memory leaks.

### Notes

- Ensure that the `gdiplus.bas` file is correctly included and contains all necessary GDI+ declarations.
- Replace `"image.jpg"` with the path to your desired image file.
- This code assumes basic knowledge of COM programming and GDI+. If you encounter issues, consult Microsoft's documentation for more details on these APIs.

But looks like a bit Pseudocode not 100% Powerbasic, lets try TÜLU 3.

#COMPILE EXE
#DIM ALL
#INCLUDE "WIN32API.INC"

' GDI+ Flat API Declarations
DECLARE FUNCTION GdiplusStartup LIB "GDIPLUS.DLL" ALIAS "GdiplusStartup" (token AS DWORD, input AS GdiplusStartupInput, output AS DWORD) AS LONG
DECLARE SUB GdiplusShutdown LIB "GDIPLUS.DLL" ALIAS "GdiplusShutdown" (BYVAL token AS DWORD)

' GDI+ Startup Input Structure
TYPE GdiplusStartupInput
    GdiplusVersion AS LONG
    DebugEventCallback AS DWORD
    SuppressBackgroundThread AS BOOL
    SuppressExternalCodecs AS BOOL
END TYPE

' GDI+ Bitmap Loading and Conversion
DECLARE FUNCTION GdipCreateBitmapFromFile LIB "GDIPLUS.DLL" ALIAS "GdipCreateBitmapFromFile" (BYVAL filename AS ASCIIZ PTR, bitmap AS DWORD) AS LONG
DECLARE FUNCTION GdipCreateHBITMAPFromBitmap LIB "GDIPLUS.DLL" ALIAS "GdipCreateHBITMAPFromBitmap" (BYVAL bitmap AS DWORD, BYVAL background AS DWORD, hbmReturn AS DWORD, color AS DWORD) AS LONG

' OLE and DataObject
DECLARE FUNCTION OleCreateStaticFromData LIB "OLE32.DLL" ALIAS "OleCreateStaticFromData" (BYVAL pSrcDataObj AS DWORD, BYREF pFormatEtc AS FormatEtc, pClientSite AS DWORD, pStg AS DWORD, ppvObj AS DWORD) AS LONG

' FormatEtc and STGMEDIUM Structures
TYPE FormatEtc
    cfFormat AS WORD
    ptd      AS DWORD
    dwAspect AS DWORD
    lindex   AS LONG
    tymed    AS DWORD
END TYPE

TYPE STGMEDIUM
    tymed         AS DWORD
    union
        hBitmap       AS DWORD
        ' ... other members
    END UNION
    pUnkForRelease AS DWORD
END TYPE

' Main Function
FUNCTION PBMAIN () AS LONG
    LOCAL token AS DWORD
    LOCAL startupInput AS GdiplusStartupInput
    LOCAL pBitmap AS DWORD
    LOCAL hBitmap AS DWORD
    LOCAL formatEtc AS FormatEtc
    LOCAL stgMedium AS STGMEDIUM
    LOCAL pDataObject AS DWORD
    LOCAL pStaticObj AS DWORD

    ' Initialize GDI+
    startupInput.GdiplusVersion = 1
    IF GdiplusStartup(token, startupInput, BYVAL 0&) <> 0 THEN
        MSGBOX "Failed to initialize GDI+"
        EXIT FUNCTION
    END IF

    ' Load the image file
    IF GdipCreateBitmapFromFile("path\to\your\image.jpg", pBitmap) <> 0 THEN
        MSGBOX "Failed to load image"
        GOTO Cleanup
    END IF

    ' Create a GDI bitmap from the GDI+ bitmap
    IF GdipCreateHBITMAPFromBitmap(pBitmap, 0, hBitmap, 0) <> 0 THEN
        MSGBOX "Failed to create bitmap"
        GOTO Cleanup
    END IF

    ' Prepare the FormatEtc structure
    formatEtc.cfFormat = %CF_BITMAP
    formatEtc.dwAspect = %DVASPECT_CONTENT
    formatEtc.lindex = -1
    formatEtc.ptd = 0
    formatEtc.tymed = %TYMED_GDI

    ' Prepare the STGMEDIUM structure
    stgMedium.tymed = %TYMED_GDI
    stgMedium.hBitmap = hBitmap

    ' Create the DataObject COM object (simplified, actual implementation may vary)
    ' pDataObject = ... (create and initialize IDataObject)

    ' Create a static object from the data
    IF OleCreateStaticFromData(pDataObject, formatEtc, 0, 0, pStaticObj) <> %S_OK THEN
        MSGBOX "Failed to create static object"
        GOTO Cleanup
    END IF

    ' Use the static object as needed
    ' ...

Cleanup:
    ' Release GDI+ resources
    IF hBitmap THEN DeleteObject hBitmap
    IF pBitmap THEN GdipDisposeImage pBitmap

    ' Shutdown GDI+
    GdiplusShutdown token

END FUNCTION


Does not look perfect. et's see what Deepseek provides:


#COMPILE EXE
#DIM ALL

' Include necessary Windows and GDI+ headers
#INCLUDE "win32api.inc"
#INCLUDE "gdiplus.inc"

' Constants
%CF_BITMAP = 2
%TYMED_GDI = 1

' GDI+ function declarations
DECLARE FUNCTION GdipCreateBitmapFromFile LIB "gdiplus.dll" ALIAS "GdipCreateBitmapFromFile" _
    (BYVAL filename AS WSTRINGZ, BYREF bitmap AS DWORD) AS LONG
DECLARE FUNCTION GdipCreateHBITMAPFromBitmap LIB "gdiplus.dll" ALIAS "GdipCreateHBITMAPFromBitmap" _
    (BYVAL bitmap AS DWORD, BYREF hbmReturn AS DWORD, BYVAL background AS DWORD) AS LONG
DECLARE FUNCTION GdipDisposeImage LIB "gdiplus.dll" ALIAS "GdipDisposeImage" _
    (BYVAL image AS DWORD) AS LONG

' IDataObject interface GUID
$IID_IDataObject = GUID$("{0000010E-0000-0000-C000-000000000046}")

' Main function
FUNCTION PBMAIN() AS LONG
    LOCAL hBitmap AS DWORD
    LOCAL gdiplusToken AS DWORD
    LOCAL pBitmap AS DWORD
    LOCAL hr AS LONG
    LOCAL pDataObject AS IDataObject PTR
    LOCAL formatEtc AS FORMATETC
    LOCAL stgMedium AS STGMEDIUM

    ' Initialize GDI+
    LOCAL gdiplusStartupInput AS GdiplusStartupInput
    gdiplusStartupInput.GdiplusVersion = 1
    hr = GdiplusStartup(gdiplusToken, gdiplusStartupInput, BYVAL %NULL)
    IF hr <> %S_OK THEN
        MSGBOX "Failed to initialize GDI+"
        EXIT FUNCTION
    END IF

    ' Load the image file
    hr = GdipCreateBitmapFromFile("C:\path\to\your\image.jpg", pBitmap)
    IF hr <> %S_OK THEN
        MSGBOX "Failed to load image"
        GdiplusShutdown(gdiplusToken)
        EXIT FUNCTION
    END IF

    ' Convert the image to a bitmap handle
    hr = GdipCreateHBITMAPFromBitmap(pBitmap, hBitmap, 0)
    IF hr <> %S_OK THEN
        MSGBOX "Failed to create HBITMAP"
        GdipDisposeImage(pBitmap)
        GdiplusShutdown(gdiplusToken)
        EXIT FUNCTION
    END IF

    ' Prepare FORMATETC structure
    formatEtc.cfFormat = %CF_BITMAP
    formatEtc.ptd = %NULL
    formatEtc.dwAspect = %DVASPECT_CONTENT
    formatEtc.lindex = -1
    formatEtc.tymed = %TYMED_GDI

    ' Prepare STGMEDIUM structure
    stgMedium.tymed = %TYMED_GDI
    stgMedium.hBitmap = hBitmap
    stgMedium.pUnkForRelease = %NULL

    ' Create a DataObject COM object (simplified for demonstration)
    ' In a real implementation, you would need to implement the IDataObject interface.
    pDataObject = COM CREATE $IID_IDataObject
    IF ISFALSE(pDataObject) THEN
        MSGBOX "Failed to create DataObject"
        DeleteObject(hBitmap)
        GdipDisposeImage(pBitmap)
        GdiplusShutdown(gdiplusToken)
        EXIT FUNCTION
    END IF

    ' Use OleCreateStaticFromData to create a static object (not shown here)
    ' You would pass pDataObject to OleCreateStaticFromData.

    ' Clean up
    COM RELEASE pDataObject
    DeleteObject(hBitmap)
    GdipDisposeImage(pBitmap)
    GdiplusShutdown(gdiplusToken)

    MSGBOX "Success!"
END FUNCTION


This really looks like Powerbasic, and it was not even the model R1.


Charles Pegge

Perhaps the developers at Microsoft are incentivized to add these layers of complexity. It keeps them employed. GDIplus itself is very old and very powerful, as is RichEdit, and their APIs are quite easy to handle. José has demonstrated that it requires great determination to crack these higher layers.

I use RichEdit in my Peroxide IDE instead of Scintilla, and colorizing keywords is still possible. And GDIplus is essential for loading and saving images, including Opengl textures.

José Roca

#23
I will implement IDataObject if I can find enough information about how to make it. For the moment is not necessary because the InsertImage method works like a charm.

After implementing IRichEditOleCallback, two important methods to load RTF files and save the contents to a file, also work fine.

Saving the contents of the rich edit control to a file is as easy as:

DIM strRTFData AS STRING = pRichEdit->GetRTFText
DIM hFile AS HANDLE = CreateFileW("Test1.rtf", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)
IF hFile THEN
   DIM dwBytesWritten AS DWORD
   DIM bSuccess AS BOOLEAN = WriteFile(hFile, STRPTR(strRTFData), LEN(strRTFData), @dwBytesWritten, NULL)
   CloseHandle(hFile)
END IF

To make it even easier, I will add a save method with parameters for the path and file name and a boolean value to overwrite or not the file if it already exists.


José Roca


Theo Gottwald

@José Roca
Just ask Qwen KI he knows that.

Certainly! Implementing `IDataObject` in .NET (typically used in the context of drag-and-drop, clipboard operations, or OLE data transfers) can be a bit tricky because it involves working with COM (Component Object Model) interfaces. However, I'll provide you with an overview of what `IDataObject` is and how to implement it.

### What is IDataObject?

`IDataObject` is an interface that provides a standard mechanism for transferring data between applications. It is commonly used in scenarios such as:

- **Clipboard operations**: When copying or pasting data between applications.
- **Drag-and-Drop**: When dragging data from one control or application to another.
- **OLE Data Transfer**: When embedding or linking objects between applications.

The `IDataObject` interface is defined in the `System.Windows.Forms` namespace and is part of the .NET Framework. It allows you to store and retrieve data in various formats.

### Key Methods of IDataObject

The `IDataObject` interface has several key methods that you need to implement:

1. **GetData**: Retrieves the data associated with a specified format.
2. **GetDataPresent**: Checks whether the data object contains data in a specific format.
3. **GetFormats**: Returns a list of all the formats that the data object supports.
4. **SetData**: Stores data in the data object in a specified format.

### How to Implement IDataObject

To implement `IDataObject`, you need to create a class that implements these methods. Here's a basic example of how you might implement `IDataObject`:

```csharp
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class MyDataObject : IDataObject
{
    private readonly Dictionary<string, object> _data = new Dictionary<string, object>();

    // IDataObject implementation

    public object GetData(string format)
    {
        if (_data.ContainsKey(format))
        {
            return _data[format];
        }
        return null;
    }

    public object GetData(Type format)
    {
        return GetData(format.FullName);
    }

    public object GetData(string format, bool autoConvert)
    {
        // In this simple implementation, we ignore autoConvert
        return GetData(format);
    }

    public bool GetDataPresent(string format)
    {
        return _data.ContainsKey(format);
    }

    public bool GetDataPresent(Type format)
    {
        return GetDataPresent(format.FullName);
    }

    public bool GetDataPresent(string format, bool autoConvert)
    {
        // In this simple implementation, we ignore autoConvert
        return GetDataPresent(format);
    }

    public string[] GetFormats()
    {
        return GetFormats(false);
    }

    public string[] GetFormats(bool autoConvert)
    {
        // In this simple implementation, we ignore autoConvert
        return _data.Keys.ToArray();
    }

    public void SetData(object data)
    {
        SetData(data.GetType(), data);
    }

    public void SetData(Type format, object data)
    {
        SetData(format.FullName, data);
    }

    public void SetData(string format, object data)
    {
        _data[format] = data;
    }

    public void SetData(string format, bool autoConvert, object data)
    {
        // In this simple implementation, we ignore autoConvert
        SetData(format, data);
    }
}
```

### Explanation of the Implementation

- **_data**: A dictionary that holds the data in different formats. The key is the format (e.g., "Text", "Bitmap"), and the value is the actual data.
 
- **GetData**: This method retrieves the data for a given format. If the format exists in the `_data` dictionary, it returns the corresponding data; otherwise, it returns `null`.

- **GetDataPresent**: This method checks whether the data object contains data in a specific format. It simply checks if the format exists in the `_data` dictionary.

- **GetFormats**: This method returns an array of all the formats that the data object supports. In this case, it just returns the keys from the `_data` dictionary.

- **SetData**: This method stores data in the data object. You can specify the format explicitly or let the method infer the format from the type of the data.

### Using the Custom IDataObject

You can use your custom `IDataObject` implementation in scenarios like drag-and-drop or clipboard operations. Here's an example of using it with the clipboard:

```csharp
public class Program
{
    public static void Main()
    {
        // Create an instance of your custom IDataObject
        MyDataObject myDataObject = new MyDataObject();

        // Set some data
        myDataObject.SetData(DataFormats.Text, "Hello, World!");
        myDataObject.SetData(DataFormats.Bitmap, new System.Drawing.Bitmap(100, 100));

        // Copy the data to the clipboard
        Clipboard.SetDataObject(myDataObject);

        // Retrieve data from the clipboard
        IDataObject retrievedData = Clipboard.GetDataObject();

        if (retrievedData.GetDataPresent(DataFormats.Text))
        {
            string text = (string)retrievedData.GetData(DataFormats.Text);
            Console.WriteLine("Clipboard Text: " + text);
        }

        if (retrievedData.GetDataPresent(DataFormats.Bitmap))
        {
            var bitmap = (System.Drawing.Bitmap)retrievedData.GetData(DataFormats.Bitmap);
            Console.WriteLine("Clipboard contains a bitmap.");
        }
    }
}
```

### Notes

- **AutoConvert**: In this simple implementation, we ignored the `autoConvert` parameter. In more advanced implementations, you might want to handle automatic conversion between formats (e.g., converting text to Unicode).
 
- **COM Interop**: If you're working with OLE data transfer or other COM-based scenarios, you may need to deal with COM interop and implement additional interfaces like `IComDataObject`. This is more complex and typically requires marshaling data between managed and unmanaged code.

- **Data Formats**: Common data formats include `DataFormats.Text`, `DataFormats.Bitmap`, `DataFormats.FileDrop`, etc. You can also define custom formats if needed.

### Conclusion

This is a basic implementation of `IDataObject`. Depending on your use case, you might need to extend it further, especially if you're dealing with more complex scenarios like OLE data transfer or custom clipboard formats.

If you have any specific questions or need further clarification, feel free to ask!

José Roca

> @José Roca
> Just ask Qwen KI he knows that.

Thanks, but as I'm not using C#, that code is completely useless to me.

I already have a couple of ideas of how to do it.

José Roca

I have added methods to save the entire contents or a selection of the contents of a rich edit control in RTF format (with or without the inclusion of inserted images or COM objects) or in text format.

Theo Gottwald

I am still using Bob's PowerBasic and i do not plan to switch until Jürgen has something better.
He is still working on it. While there are some strange symptoms when compiling (sometimes no need to declare local variables, sometimes Labels and variables, or Labels and Function names get into conflict) i did not run against a limit.
Must say that the whole Object things that was implemented lastly seems to produce memory leaks so i do not use it for stuff that is often called.

José Roca

#29
A new compiler made by a man alone is a project of at least 10 years (ask Charles). I'm too old to wait. FreeBasic offers what I need and all the code that I write can be compiled to 32 or 64 bit without changes.

BTW I'm going to add methods to work easily with fonts: Bold, Italic, StrikeOut, Underline, Height, FaceName, Offset and color. Having to fill a CHARFORMAT structure and read the documentation each time to see which values and flags you have to set is very tedious and time consuming.

So, instead of
`
pRichEdit->ExSetSel(98, 113)          ' // Select word at position 98, 113
DIM cf AS CHARFORMAT
cf.dwMask = CFM_BOLD                  ' // Character effects
cf.dwEffects = CFE_BOLD               ' // The CFE_BOLD value of the dwEffects member is valid
pRichEdit->SelectionCharFormat = cf   ' // Set the color
pRichEdit->HideSelection(TRUE)        ' // Hide selection

you will use

pRichEdit->SetSelFontBold(98,113)