1. Jeff Kandt
  2. PowerBuilder
  3. Friday, 4 October 2024 06: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)

Responses (2)
  1. Likes
  2. Latest
  3. Oldest
Loading...

Find Questions by Tag

.EXE .NET 6.0 .NET Assembly .NET Core 3.1 .NET Core Framework .NET DataStore .NET Std Framework 32-bit 64-bit ADO.NET AEM AI Algorithm Amazon AWS Android Apache API APK App Store App Store (Apple) Appeon Workspace Appeon Xcelerator Plug-in Architecture Array ASE Asynchronous Methods Authentication AutoBuild AutoCompiler Automated Testing Automation AutoScript Azure Barcode Base64 Batch BigData BLOB Branch & Merge Browser Bug Build Button C# C# Class Importer C# Editor C# Model generator Calendar Camera Certificate Chrome Citrix Class Client Client/Server Cloud Cluster Collection COM Command Line Compiler Compression Computed Field Configuration Controls Cookies Cordova Crash Cross-Platform Crosstab CSharpAssembly CSharpObject CSS CSV Cursor Data Database Database Driver Database Painter Database Profile Database Provider DataObject DataSource DataStore DataStore (C#) DataStore (PS) DataType DataWindow DATE DATETIME DB2 Debug Debugger Debugging Deployment Design DLL DO-WHILE Dockable Docker Documentation DOUBLE Download DragDrop Edge Edit Style Editor Elevate Conference Email Embedded SQL Emulator Encoding Encryption Enhancement Request Entity Entity Framework ERP Error Event Event Handler Event Handling Excel Exception Export Expression External Functions F# Field File File Access Filter Firefox Firewall Font FOR-NEXT Foreground Format Function Garbage Collection GeoLocation Git Graph HANA Hash Header HTML/5 HTTP/S HTTPClient Icon IDE Identity IIS IMAPI Import InfoMaker Inheritance Installation Integer IntelliSense Interface Internet Internet Explorer iOS IPA iPad iPhone IWA J# Java JavaScript JBoss JDBC JOIN JSON JSONGenerator JSONParser Kestrel Label Lambda Large File LDAP Library License LINQ Linux OS Load Balancing Localization Localized PBVM Log In Log Out Logging LONG LONGLONG macOS MAPI Maps MDI Memory Memory Leak Menu Merge MessageBox Messagging Method Migration MIME TYPE Mobile Model ModelStore ModelStore (C#) MSOLEDBSQL Multi Threading MVC MySQL n-Tier Namespace NativePDF NVO OAuth ODATA ODBC Office Offline OLE OLEDB Online Open Source OpenAPI OpenSSL Oracle OrcaScript Other Outlook Output Package Parameter Patch PayPal PB Classic PB Native PB.NET PBC PBD PBDOM PBG PBJVM PBL PBNI PBORCA PBVM PBX PDF Performance Permission PFC Picture Pipeline Play Store (Google) Plugin Popup Port POST PostgreSQL PowerBuilder PowerBuilder (Appeon) PowerBuilder (SAP) PowerBuilder Compiler PowerBuilder Runtime PowerClient PowerScript (PS) PowerScript IDE PowerScript Migrator PowerServer PowerServer Mobile PowerServer Toolkit PowerServer Web PowerServerLabel Print Properties Proxy Publish PULL PUSH Query Regression Release Renew Resize Response REST Retrieve RibbonBar RibbonBar Builder Rich Text Roadmap RPC Runtime Packager SaaS Scaffolding Script SDI SDK Security Server Service Session Single Sign-on Size SMTP SMTPClient SnapDevelop SOAP Sort Source Code Speech Recognition SQL SQL Anywhere SQL Server SqlBuilder SqlExecutor SQLite SqlModelMapper Storage Stored Procedure Subscription SVN Swagger Syntax TabbedBar TabbedView Tablet TabPage Target TE Control Testing Text TFS Theme TIME Timer TLS/SSL Tomcat TortoiseGit TortoiseSVN Transaction Transparency Trial Trigger TRY-CATCH TX Control Type UI ULONG UltraLite Uninstall Unit Test Unit Testing UNIX OS Update Upgrade Upload URL User Center User Object UWP Validation VARCHAR Variable Versioning Visual Studio Visual Studio Code VM Voice Warning WCF Web API Web Extensions Web Service WebBrowser WebForms WebLogic WebSphere WildFly WinAPI Window Windows OS WinForms Wizard Workgroup Workspace WPF XCODE XHTML XML Zoom

Helpful?

If a reply or comment is helpful for you, please don’t hesitate to click the Helpful button. This action is further confirmation of their invaluable contribution to the Appeon Community.