1. Jeff Kandt
  2. PowerBuilder
  3. Friday, 4 October 2024 18:28 PM UTC

We've been struggling, like a lot of people apparently, with providing the ability to drag messages and attachments from Outlook into our PowerBuilder app.

We had been using  Dddll.dll from Catsoft, but that stopped working when Outlook went to 64-bit, since we haven't yet migrated our app to 64-bit.

We looked at the clever method of using OLE described here: (https://community.appeon.com/index.php/qna/q-a/drag-and-drop-emails-from-64-bit-outlook) but found it has a number of functional disadvantages. Specifically, Dddll.dll is still used to notify PB when something got dropped from Outlook, but it doesn't communicate exactly what was dropped. So it then makes an OLE connection to Outlook and calls methods to determine what is currently selected within Outlook and then tells Outlook to export the selected message(s).

The problem with this is that what's selected isn't always what was dragged. In the example code, when the user drags attachments out of a message window, the entire message gets exported instead. And if after opening Message 1 the user selects Message 2 in their Inbox before dragging out of the Message 1 window, the Message 2 was exported instead of the one being dragged out of. In trying to fix these issues we found that there are some situations in which it appears to be impossible via OLE to determine what was actually dragged out of Outlook. As one worst-case example: When the user is dragging out of a preview pane in their Inbox, the user may have multiple types of entities selected at once: attachments selected in the preview pane, plus messages selected in the list. Did the user drag the message(s) or attachment(s)?

In the end, we found what I believe to be a better solution that I wanted to share here: A webbrowser object used as a drag target. The page loaded in the webbrowser contains Javascript to access the HTML5 Drag-and-Drop and File APIs, and passes the name and binary data from the dropped files into PowerBuilder by calling a PB event. In order to be called from Javascript, the PB event can only accept a single string argument, so the file data is encoded to base 64.

One thing I ran into was an apparent limit on the size of the string that can be passed into the PB event. To get around that, the javascript "chunks" the file data into 32k pieces and calls the event for each chunk. The PB event then has to re-assemble the chunks.

The new webbrowser control's Chromium-based engine has excellent support for drag-and-drop, with no apparent 32/64 bit issues. It is able to pull messages and attachments out of Outlook, as well as files from File Explorer. As a special bonus in our environment where some users run our app via Citrix, it even supports dragging across the local/remote boundary: You can drag messages and attachments out of a locally running instance of Outlook and into our remotely hosted PB app.

As noted in the post on the OLE method, the "New Outlook" doesn't support drag and drop yet, so until Microsoft fixes this we'll be encouraging our users to switch back to the "old" Outlook if they want any of this to work.

The main disadvantage of this method is that it doesn't enable dropping directly into an existing PB control, such as a datawindow, like we could before. Instead. the browser control must be a separate visible "Drop files here" area on the window, specifically used only as a drag target.  I tried various ways to make the browser control transparent and position it over the PB control so that it looks like you can drop onto an existing control, but couldn't make it work -- please let me know if anyone figures that out.

I've uploaded a zip file containing a sample application on CodeExchange: https://community.appeon.com/index.php/codeexchange/powerbuilder/364-drag-and-drop-from-outlook-using-webbrowser-control-drop-target

 

 

Steps to implement:

1) Create a webbrowser control on your window

2) In the window's open event, navigate the browsercontrol to the static page containing the special javascript. In my example that page is called "dd.html":

browsercontrol_1.Navigate("file://C:\[path]\dd.html")

3) In the browsercontrol's "navigationstart" event, register the PB event to be called from the page's Javascript. In my example the event is called "ue_filedropchunk":

This.RegisterEvent("ue_filedropchunk")

4) Create instance variables on the browsercontrol to allow the event to keep track of file chunks across multiple event calls:

string is_fileName, is_fileData

integer ii_totalChunks

integer ii_receivedChunks

5) Create a "ue_filedropchunk" event on the webbrowser that accepts a single string argument called "as_data":

string ls_fileName, ls_chunkData, ls_decodedBlob
integer li_chunkIndex, li_totalChunksInMessage
long ll_pos1, ll_pos2, ll_pos3, ll_file, ll_row, ll_byteswritten//, ll_stringsize, ll_chunksize, ll_blobsize
CoderObject lco_coder
blob lbl_chunkBlob, lbl_decodedBlob 

// Parse the incoming string (fileName|chunkIndex|totalChunks|chunkData)

ll_pos1 = Pos(as_data, "|")
ll_pos2 = Pos(as_data, "|", ll_pos1 + 1)
ll_pos3 = Pos(as_data, "|", ll_pos2 + 1)
 
ls_fileName = Left(as_data, ll_pos1 - 1)
li_chunkIndex = Integer(Mid(as_data, ll_pos1 + 1, ll_pos2 - ll_pos1 - 1))
li_totalChunksInMessage = Integer(Mid(as_data, ll_pos2 + 1, ll_pos3 - ll_pos2 - 1))
ls_chunkData = Mid(as_data, ll_pos3 + 1)

 // Check if this is the first chunk (initialize global variables)

if li_chunkIndex = 0 then
     is_fileName = ls_fileName
     is_filedata = ""
     ii_totalChunks = li_totalChunksInMessage
     ii_receivedChunks = 0

end if

// Append the current chunk to the global string
is_fileData = is_fileData + ls_chunkData
ii_receivedChunks++

// Check if all chunks have been received

if ii_receivedChunks = ii_totalChunks then

     // All chunks received, save the file
     lco_coder = Create CoderObject
     lbl_decodedBlob = lco_coder.Base64Decode(is_fileData)
     Destroy lco_coder

     ll_file = FileOpen("C:\temp\" + is_fileName, StreamMode!, Write!)

     if ll_file <> -1 then

          ll_byteswritten = FileWriteEx(ll_file,  lbl_decodedBlob)
          FileClose(ll_file)

          //ll_row = dw_1.InsertRow(0)
          //dw_1.Object.Files[ll_row] = ls_FileName
     else

          MessageBox("Error", "Failed to save the file.")
     end if

     // Reset instance variables
     is_fileName = ""
     is_filedata = ""
     ii_totalChunks = 0
     ii_receivedChunks = 0

end if

6) Finally, create the html page and save it in the location referenced by the navigation in the window's open event. In my example, the page implements the necessary javascript, and also visually displays a dashed rounded border around the entire page, with an upload icon and "Drop files here" text, to clearly identify the drag target to the user. I'll let you find your own icon file, and adjust to CSS to match your own needs, such as the page background color:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Centered Image with Caption</title>
    <style>
        html {
            height: 100%;
            margin: 0;
            padding: 8px;
            width: 100%;
            box-sizing: border-box;
       }
       body {
            background-color: LightGrey;
            height: 100%;
            margin: 0;
            padding: ;
            width: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            border: 3px dashed DarkSlateGrey;
            border-radius: 15px;
        }
        .container {
            text-align: center;
            width: 100%;
        }
        .centered-image {
            max-height: 80px;
            height: 100vh;
            width: auto;
            max-width: 80%;
            opacity: .5;
        }
        .caption {
            margin-top: 5px;
            margin-bottom: 5px;
            font-size: 24px;
            font-family: 'Arial', sans-serif;
            font-weight: bold;
            color: #333;
            opacity: .8;
        }
    </style>
</head>
<body>
    <div id="drop-zone" class="container">
        <img src="/cloud_arrow_down_icon.png" alt="Cloud Image" class="centered-image">
        <p class="caption">Drop files here</p>
    </div>
<script>
        // Function to handle file drop event
        function handleDrop(event) {
            event.preventDefault();  // Prevent default behavior
            const files = event.dataTransfer.files;  // Get dropped files
            if (files.length > 0) {
                for (let i = 0; i < files.length; i++) {
                    let file = files[i];
                    readAndSendFile(file);  // Read and send each file
                }
            }
        }
 
const CHUNK_SIZE = 32000;  // Set chunk size to 32 KB (or smaller, depending on the limit)
 
// Function to read the file and send it to PowerBuilder
function readAndSendFile(file) {
const reader = new FileReader();
 
reader.onload = function(e) {
const base64Content = e.target.result.split(",")[1];  // Extract base64 content
const fileName = file.name;
const totalChunks = Math.ceil(base64Content.length / CHUNK_SIZE);
 
// Send the file in chunks

for (let i = 0; i < totalChunks; i++) {
const chunk = base64Content.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
const chunkData = `${fileName}|${i}|${totalChunks}|${chunk}`;  // Include chunk index and total chunks

// Send each chunk to PowerBuilder

window.webBrowser.ue_filedropchunk(chunkData);  // Send chunk data to PowerBuilder
}
};

// Read the file as a Base64 string
reader.readAsDataURL(file);
}
 
 
        // Prevent default behavior for dragover event
        function handleDragOver(event) {
            event.preventDefault();
        }
 
        // Initialize drag-and-drop zone
        window.onload = function() {
            const dropZone = document.getElementById('drop-zone');
            dropZone.addEventListener('dragover', handleDragOver);  // Set dragover event
            dropZone.addEventListener('drop', handleDrop);  // Set drop event
        };
   </script>
</body>
</html>

…That's it! I think I've included all the necessary pieces, but let me know if you have any issues. And I'm sure this can be improved upon, so please share anything you discover.

Attachments (1)
Accepted Answer
Jeff Kandt Accepted Answer Pending Moderation
  1. Saturday, 5 October 2024 22:23 PM UTC
  2. PowerBuilder
  3. # Permalink
Comment
  1. Christopher Craft
  2. Tuesday, 22 October 2024 00:55 AM UTC
Jeff,



Do you think the Browser could support the 'Copy/Paste' option too? Also, could we fire an event when the user is dragging? I want to use my same Browser control to do this work.



Chris Craft
  1. Helpful
There are no comments made yet.
John Fauss Accepted Answer Pending Moderation
  1. Friday, 4 October 2024 18:55 PM UTC
  2. PowerBuilder
  3. # 1

VERY nice, Jeff! A very creative solution. Well done!

Would you please consider creating a small, example app that implements this and post it in The CodeXchange portion of the Community?

    https://community.appeon.com/index.php/codexchange/share-code

This would be a great addition to the PowerBuilder section of CodeXchange.

Sometimes when posting in CodeXchange, I include in the zip file that contains the .pbl, .pbt, and .pbw a Word document with some explanatory text , such as what you've posted here in this Q&A thread. Doing this would ensure the helpful explanation you've written would be kept with the example app in one place.

Best regards, John

 

Comment
  1. Miguel Leeuwe
  2. Tuesday, 8 October 2024 07:28 AM UTC
Jeff,

"too edge-case to matter to you"? I like your attitude!

The world isn't perfect and more knowing MS doesn't stop changing their stuff, just wait for 2029.

What we do, is educate our users what they can and what they cannot do when running a 32 bit PB app and having office 64 bits and still want to drag and drop.

regards.
  1. Helpful
  1. Miguel Leeuwe
  2. Tuesday, 8 October 2024 07:32 AM UTC
"Unfortunately, only developers are complaining about the lack of OLE,"

That's because there's a ton of code that would have to be changed without OLE.
  1. Helpful
  1. David Peace (Powersoft)
  2. Tuesday, 8 October 2024 13:16 PM UTC
Very nice solution, thanks
  1. Helpful
There are no comments made yet.
  • Page :
  • 1


There are no replies made for this question yet.
However, you are not allowed to reply to this question.