Jump to content

Lightweight Notepad Clone (incomplete)


Go to solution Solved by MattyD,

Recommended Posts

Posted (edited)

It looks like we need to call IFileOpenDialog::GetResults() - and that returns a shellItem collection.  It's possible for the collection to be empty though - so we should check for that with $oItemArr.GetCount()

#include <WinAPI.au3>
#include <GuiConstants.au3>

Global $sCLSID_FileOpenDialog = "{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}"
Global Const $sIID_IFileOpenDialog = "{d57c7288-d4ad-4768-be02-9d969532d960}"
Global Const $sIID_IFileDialogCustomize = "{e6fdd21a-163f-4975-9c8c-a69f1ba37034}"
Global Const $sIID_IShellItemArray = "{b63ea76d-1f85-456f-a19c-48159efa858b}"
Global Const $sIID_IShellItem = "{43826d1e-e718-42ee-bc55-a1e261c37bfe}"

Global Const $tagIFileOpenDialog = _
    "Show hresult(hwnd);" & _
    "SetFileTypes hresult(uint;ptr);" & _
    "SetFileTypeIndex hresult(uint);" & _
    "GetFileTypeIndex hresult(uint*);" & _
    "Advise hresult(ptr;dword*);" & _
    "Unadvise hresult(dword);" & _
    "SetOptions hresult(dword);" & _
    "GetOptions hresult(dword*);" & _
    "SetDefaultFolder hresult(ptr);" & _
    "SetFolder hresult(ptr);" & _
    "GetFolder hresult(ptr*);" & _
    "GetCurrentSelection hresult(ptr*);" & _
    "SetFileName hresult(wstr);" & _
    "GetFileName hresult(wstr*);" & _
    "SetTitle hresult(wstr);" & _
    "SetOkButtonLabel hresult(wstr);" & _
    "SetFileNameLabel hresult(wstr);" & _
    "GetResult hresult(ptr*);" & _
    "AddPlace hresult(ptr;dword);" & _
    "SetDefaultExtension hresult(wstr);" & _
    "Close hresult(hresult);" & _
    "SetClientGuid hresult(clsid);" & _
    "ClearClientData hresult();" & _
    "SetFilter hresult(ptr);" & _
    "GetResults hresult(ptr*);" & _
    "GetSelectedItems hresult(ptr*);"

Global Const $tagIFileDialogCustomize = _
    "EnableOpenDropDown hresult(dword);" & _
    "AddMenu hresult(dword;wstr);" & _
    "AddPushButton hresult(dword;wstr);" & _
    "AddComboBox hresult(dword);" & _
    "AddRadioButtonList hresult(dword);" & _
    "AddCheckButton hresult(dword;wstr;bool);" & _
    "AddEditBox hresult(dword;wstr);" & _
    "AddSeparator hresult(dword);" & _
    "AddText hresult(dword;wstr);" & _
    "SetControlLabel hresult(dword;wstr);" & _
    "GetControlState hresult(dword;dword*);" & _
    "SetControlState hresult(dword;dword);" & _
    "GetEditBoxText hresult(dword;ptr*);" & _
    "SetEditBoxText hresult(dword;wstr);" & _
    "GetCheckButtonState hresult(dword;bool*);" & _
    "SetCheckButtonState hresult(dword;bool);" & _
    "AddControlItem hresult(dword;dword;wstr);" & _
    "RemoveControlItem hresult(dword;dword);" & _
    "RemoveAllControlItems hresult(dword);" & _
    "GetControlItemState hresult(dword;dword;dword*);" & _
    "SetControlItemState hresult(dword;dword;dword);" & _
    "GetSelectedControlItem hresult(dword;dword*);" & _
    "SetSelectedControlItem hresult(dword;dword);" & _
    "StartVisualGroup hresult(dword;wstr);" & _
    "EndVisualGroup hresult();" & _
    "MakeProminent hresult(dword);" & _
    "SetControlItemText hresult(dword;dword;wstr);"

Global Const $tagIShellItemArray = _
    "BindToHandler hresult(ptr;clsid;clsid;ptr*);" & _
    "GetPropertyStore hresult(int;clsid;ptr*);" & _
    "GetPropertyDescriptionList hresult(ptr;clsid;ptr*);" & _
    "GetAttributes hresult(int;int;int*);" & _
    "GetCount hresult(uint*);" & _
    "GetItemAt hresult(uint;ptr*);" & _
    "EnumItems hresult(ptr*);"

Global Const $tagIShellItem = _
    "BindToHandler hresult(ptr;clsid;clsid;ptr*);" & _
    "GetParent hresult(ptr*);" & _
    "GetDisplayName hresult(int;wstr*);" & _
    "GetAttributes hresult(ulong;ulong*);" & _
    "Compare hresult(ptr;ulong;int*);"

Global Enum  $SIGDN_NORMALDISPLAY = 0, _
  $SIGDN_PARENTRELATIVEPARSING, _
  $SIGDN_DESKTOPABSOLUTEPARSING, _
  $SIGDN_PARENTRELATIVEEDITING, _
  $SIGDN_DESKTOPABSOLUTEEDITING, _
  $SIGDN_FILESYSPATH, _
  $SIGDN_URL, _
  $SIGDN_PARENTRELATIVEFORADDRESSBAR, _
  $SIGDN_PARENTRELATIVE, _
  $SIGDN_PARENTRELATIVEFORUI


DoFileOpen(0)

Func DoFileOpen($hParent)
    Local $oFileOpen = ObjCreateInterface($sCLSID_FileOpenDialog, $sIID_IFileOpenDialog, $tagIFileOpenDialog)

    Local $pCustomFileOpen, $tIID_IFileDialogCustomize  = _WinAPI_GUIDFromString($sIID_IFileDialogCustomize)
    $oFileOpen.QueryInterface(DllStructGetPtr($tIID_IFileDialogCustomize), $pCustomFileOpen)
    Local $oCustomFileOpen = ObjCreateInterface(Ptr($pCustomFileOpen), $sIID_IFileDialogCustomize, $tagIFileDialogCustomize)

    Local Enum $idVisualGroup = 100, $idCombo, $idItem1, $idItem2, $idItem3

    $oCustomFileOpen.StartVisualGroup($idVisualGroup, "&Encoding:")
    $oCustomFileOpen.AddComboBox($idCombo)
    $oCustomFileOpen.AddControlItem($idCombo, $idItem1, "Item 1")
    $oCustomFileOpen.AddControlItem($idCombo, $idItem2, "Item 2")
    $oCustomFileOpen.AddControlItem($idCombo, $idItem3, "Item 3")
    $oCustomFileOpen.SetSelectedControlItem($idCombo, $idItem1)
    $oCustomFileOpen.EndVisualGroup()

    $oFileOpen.Show($hParent)

    Local $idSelItem, $sSelItem
    $oCustomFileOpen.GetSelectedControlItem($idCombo, $idSelItem)

    ConsoleWrite("SelItemId = " & $idSelItem & @CRLF)

    Local $pItemArr, $pItem, $sDispName
    $oFileOpen.GetResults($pItemArr)
    Local $oItemArr = ObjCreateInterface(Ptr($pItemArr), $sIID_IShellItemArray, $tagIShellItemArray)
    $oItemArr.GetItemAt(0, $pItem)

    Local $oItem = ObjCreateInterface(Ptr($pItem), $sIID_IShellItem, $tagIShellItem)
    $oItem.GetDisplayName($SIGDN_NORMALDISPLAY, $sDispName)

    ConsoleWrite("SelItem 0 = " & $sDispName & @CRLF)

EndFunc

PS: I've also updated the "old way" example in the post above just for the exercise.  We still shouldn't use it - but I fixed that UI bug  by clearing the exstyle on the  nested dialog template (it was my mistake, not windows'!)

Edited by MattyD
Posted (edited)
11 hours ago, MattyD said:

It looks like we need to call IFileOpenDialog::GetResults() - and that returns a shellItem collection.  It's possible for the collection to be empty though - so we should check for that with $oItemArr.GetCount()

Thanks Matty. I spent some time on MSDN last night and this morning. COM stuff is not fun, that's for sure. I did not get as far as GetCount. But so far I've used your dialog setup as a base to learn from and switched over to Save As dialog because that is really what is needed in the end. I've replicated the Encoding types from Notepad's Save As dialog and also switched to getting the full system path because we would need full path to save.

From what I can tell, the OS does pretty much all of the logic for file type extension, whether or not user adds extension and so on. So that is nice. Here is where I left off:

#include <WinAPI.au3>
#include <GuiConstants.au3>
#include <Array.au3>

Global Const $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = Ptr(-4)
DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "int_ptr", $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)

Global Const $sCLSID_FileSaveDialog = "{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}"
Global Const $sIID_IFileSaveDialog = "{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}"
Global Const $sIID_IFileDialogCustomize = "{e6fdd21a-163f-4975-9c8c-a69f1ba37034}"
Global Const $sIID_IShellItem = "{43826d1e-e718-42ee-bc55-a1e261c37bfe}"

Global Const $tagIFileSaveDialog = _
    "Show hresult(hwnd);" & _
    "SetFileTypes hresult(uint;ptr);" & _
    "SetFileTypeIndex hresult(uint);" & _
    "GetFileTypeIndex hresult(uint*);" & _
    "Advise hresult(ptr;dword*);" & _
    "Unadvise hresult(dword);" & _
    "SetOptions hresult(dword);" & _
    "GetOptions hresult(dword*);" & _
    "SetDefaultFolder hresult(ptr);" & _
    "SetFolder hresult(ptr);" & _
    "GetFolder hresult(ptr*);" & _
    "GetCurrentSelection hresult(ptr*);" & _
    "SetFileName hresult(wstr);" & _
    "GetFileName hresult(wstr*);" & _
    "SetTitle hresult(wstr);" & _
    "SetOkButtonLabel hresult(wstr);" & _
    "SetFileNameLabel hresult(wstr);" & _
    "GetResult hresult(ptr*);" & _
    "AddPlace hresult(ptr;dword);" & _
    "SetDefaultExtension hresult(wstr);" & _
    "Close hresult(hresult);" & _
    "SetClientGuid hresult(clsid);" & _
    "ClearClientData hresult();" & _
    "SetFilter hresult(ptr);" & _
    "GetResults hresult(ptr*);" & _
    "GetSelectedItems hresult(ptr*);"

Global Const $tagIFileDialogCustomize = _
    "EnableOpenDropDown hresult(dword);" & _
    "AddMenu hresult(dword;wstr);" & _
    "AddPushButton hresult(dword;wstr);" & _
    "AddComboBox hresult(dword);" & _
    "AddRadioButtonList hresult(dword);" & _
    "AddCheckButton hresult(dword;wstr;bool);" & _
    "AddEditBox hresult(dword;wstr);" & _
    "AddSeparator hresult(dword);" & _
    "AddText hresult(dword;wstr);" & _
    "SetControlLabel hresult(dword;wstr);" & _
    "GetControlState hresult(dword;dword*);" & _
    "SetControlState hresult(dword;dword);" & _
    "GetEditBoxText hresult(dword;ptr*);" & _
    "SetEditBoxText hresult(dword;wstr);" & _
    "GetCheckButtonState hresult(dword;bool*);" & _
    "SetCheckButtonState hresult(dword;bool);" & _
    "AddControlItem hresult(dword;dword;wstr);" & _
    "RemoveControlItem hresult(dword;dword);" & _
    "RemoveAllControlItems hresult(dword);" & _
    "GetControlItemState hresult(dword;dword;dword*);" & _
    "SetControlItemState hresult(dword;dword;dword);" & _
    "GetSelectedControlItem hresult(dword;dword*);" & _
    "SetSelectedControlItem hresult(dword;dword);" & _
    "StartVisualGroup hresult(dword;wstr);" & _
    "EndVisualGroup hresult();" & _
    "MakeProminent hresult(dword);" & _
    "SetControlItemText hresult(dword;dword;wstr);"

Global Const $tagIShellItem = _
    "BindToHandler hresult(ptr;clsid;clsid;ptr*);" & _
    "GetParent hresult(ptr*);" & _
    "GetDisplayName hresult(int;ptr*);" & _
    "GetAttributes hresult(ulong;ulong*);" & _
    "Compare hresult(ptr;ulong;int*);"

Global Const $SIGDN_NORMALDISPLAY                 = 0x00000000
Global Const $SIGDN_PARENTRELATIVEPARSING         = 0x80018001
Global Const $SIGDN_DESKTOPABSOLUTEPARSING        = 0x80028000
Global Const $SIGDN_PARENTRELATIVEEDITING         = 0x80030001
Global Const $SIGDN_DESKTOPABSOLUTEEDITING        = 0x8004C000
Global Const $SIGDN_FILESYSPATH                   = 0x80058000
Global Const $SIGDN_URL                           = 0x80068000
Global Const $SIGDN_PARENTRELATIVEFORADDRESSBAR   = 0x80070001
Global Const $SIGDN_PARENTRELATIVE                = 0x80080001
Global Const $SIGDN_PARENTRELATIVEFORUI           = 0x80090001


Local $aReturn = DoFileSave(0)
If IsArray($aReturn) Then
    ConsoleWrite("File path and name: " & $aReturn[0] & @CRLF)
    ConsoleWrite("File encoding: " & $aReturn[1] & @CRLF)
EndIf

Func DoFileSave($hParent)
    Local $oFileSave = ObjCreateInterface($sCLSID_FileSaveDialog, $sIID_IFileSaveDialog, $tagIFileSaveDialog)

    Local $pCustomFileSave, $tIID_IFileDialogCustomize  = _WinAPI_GUIDFromString($sIID_IFileDialogCustomize)
    $oFileSave.QueryInterface(DllStructGetPtr($tIID_IFileDialogCustomize), $pCustomFileSave)
    Local $oCustomFileSave = ObjCreateInterface(Ptr($pCustomFileSave), $sIID_IFileDialogCustomize, $tagIFileDialogCustomize)

    Local $sName1 = "Text Documents (*.txt)"
    Local $sSpec1 = "*.txt"
    Local $sName2 = "All Files (*.*)"
    Local $sSpec2 = "*.*"

    Local $tName1 = DllStructCreate("wchar[" & (StringLen($sName1) + 1) & "]")
    DllStructSetData($tName1, 1, $sName1)
    Local $tSpec1 = DllStructCreate("wchar[" & (StringLen($sSpec1) + 1) & "]")
    DllStructSetData($tSpec1, 1, $sSpec1)

    Local $tName2 = DllStructCreate("wchar[" & (StringLen($sName2) + 1) & "]")
    DllStructSetData($tName2, 1, $sName2)
    Local $tSpec2 = DllStructCreate("wchar[" & (StringLen($sSpec2) + 1) & "]")
    DllStructSetData($tSpec2, 1, $sSpec2)

    Local $tFilters = DllStructCreate("ptr;ptr;ptr;ptr")
    DllStructSetData($tFilters, 1, DllStructGetPtr($tName1))
    DllStructSetData($tFilters, 2, DllStructGetPtr($tSpec1))
    DllStructSetData($tFilters, 3, DllStructGetPtr($tName2))
    DllStructSetData($tFilters, 4, DllStructGetPtr($tSpec2))

    $oFileSave.SetFileTypes(2, DllStructGetPtr($tFilters))
    $oFileSave.SetFileTypeIndex(1) ; Set Text Documents (*.txt) as default
    $oFileSave.SetDefaultExtension("txt")

    Local Enum $idVisualGroup = 100, $idCombo, $idItemANSI, $idItemUTF8, $idItemUTF16LE, $idItemUTF16BE, $idItemUTF8BOM

    $oCustomFileSave.StartVisualGroup($idVisualGroup, "&Encoding:")
    $oCustomFileSave.AddComboBox($idCombo)
    $oCustomFileSave.AddControlItem($idCombo, $idItemANSI, "ANSI")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF16LE, "UTF-16 LE")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF16BE, "UTF-16 BE")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF8, "UTF-8")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF8BOM, "UTF-8 with BOM")
    $oCustomFileSave.SetSelectedControlItem($idCombo, $idItemUTF8)
    $oCustomFileSave.EndVisualGroup()

    $oFileSave.Show($hParent)

    Local $pItem, $pDispName = 0, $sFullPath = ""
    
    $oFileSave.GetResult($pItem)

    If @error Or Not $pItem Then
        ConsoleWrite("User cancelled the Save dialog." & @CRLF)
        Return
    EndIf
    
    Local $aReturn[2]

    Local $idSelItem, $iEncode
    $oCustomFileSave.GetSelectedControlItem($idCombo, $idSelItem)
    Switch $idSelItem
        Case $idItemANSI
            $iEncode = $FO_ANSI

        Case $idItemUTF16LE
            $iEncode = $FO_UTF16_LE

        Case $idItemUTF16BE
            $iEncode = $FO_UTF16_BE

        Case $idItemUTF8
            $iEncode = $FO_UTF8_NOBOM

        Case $idItemUTF8BOM
            $iEncode = $FO_UTF8
    EndSwitch

    Local $oItem = ObjCreateInterface(Ptr($pItem), $sIID_IShellItem, $tagIShellItem)
        
    $oItem.GetDisplayName($SIGDN_FILESYSPATH, $pDispName)

    If $pDispName Then
        Local $tDispName = DllStructCreate("wchar[4096]", $pDispName)
        $sFullPath = DllStructGetData($tDispName, 1)
        $aReturn[0] = $sFullPath
        $aReturn[1] = $iEncode
        _WinAPI_CoTaskMemFree($pDispName)
    EndIf

    $oItem = 0
    _WinAPI_FreeMemory($pItem)

    Return $aReturn
EndFunc

 

Edited by WildByDesign
Added encoding to return array
Posted (edited)
50 minutes ago, WildByDesign said:

switched over to Save As dialog because that is really what is needed in the end.

Well, I was wildly wrong on this one. I only realized right now that the Notepad Open dialog actually does have an Encoding drop-down with a default Auto-Detect encoding. So it seems that we do need both. Over all the years, I don't think I've ever used the Open dialog in Notepad. That is so strange to think about. :)

Edited by WildByDesign
Posted

Here is script with Open and Save As dialogs. The part where I left off here is where the bytes need to be scanned to determine the encoding type when Auto-Detect was selected.

#include <WinAPI.au3>
#include <GuiConstants.au3>
#include <Array.au3>

Global Const $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = Ptr(-4)
DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "int_ptr", $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)

Global Const $sCLSID_FileSaveDialog = "{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}"
Global Const $sIID_IFileSaveDialog = "{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}"
Global Const $sIID_IFileDialogCustomize = "{e6fdd21a-163f-4975-9c8c-a69f1ba37034}"
Global Const $sIID_IShellItem = "{43826d1e-e718-42ee-bc55-a1e261c37bfe}"

Global Const $sCLSID_FileOpenDialog = "{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}"
Global Const $sIID_IFileOpenDialog = "{d57c7288-d4ad-4768-be02-9d969532d960}"
Global Const $sIID_IShellItemArray = "{b63ea76d-1f85-456f-a19c-48159efa858b}"

Global Const $tagIFileSaveDialog = _
    "Show hresult(hwnd);" & _
    "SetFileTypes hresult(uint;ptr);" & _
    "SetFileTypeIndex hresult(uint);" & _
    "GetFileTypeIndex hresult(uint*);" & _
    "Advise hresult(ptr;dword*);" & _
    "Unadvise hresult(dword);" & _
    "SetOptions hresult(dword);" & _
    "GetOptions hresult(dword*);" & _
    "SetDefaultFolder hresult(ptr);" & _
    "SetFolder hresult(ptr);" & _
    "GetFolder hresult(ptr*);" & _
    "GetCurrentSelection hresult(ptr*);" & _
    "SetFileName hresult(wstr);" & _
    "GetFileName hresult(wstr*);" & _
    "SetTitle hresult(wstr);" & _
    "SetOkButtonLabel hresult(wstr);" & _
    "SetFileNameLabel hresult(wstr);" & _
    "GetResult hresult(ptr*);" & _
    "AddPlace hresult(ptr;dword);" & _
    "SetDefaultExtension hresult(wstr);" & _
    "Close hresult(hresult);" & _
    "SetClientGuid hresult(clsid);" & _
    "ClearClientData hresult();" & _
    "SetFilter hresult(ptr);" & _
    "GetResults hresult(ptr*);" & _
    "GetSelectedItems hresult(ptr*);"

Global Const $tagIFileOpenDialog = _
    "Show hresult(hwnd);" & _
    "SetFileTypes hresult(uint;ptr);" & _
    "SetFileTypeIndex hresult(uint);" & _
    "GetFileTypeIndex hresult(uint*);" & _
    "Advise hresult(ptr;dword*);" & _
    "Unadvise hresult(dword);" & _
    "SetOptions hresult(dword);" & _
    "GetOptions hresult(dword*);" & _
    "SetDefaultFolder hresult(ptr);" & _
    "SetFolder hresult(ptr);" & _
    "GetFolder hresult(ptr*);" & _
    "GetCurrentSelection hresult(ptr*);" & _
    "SetFileName hresult(wstr);" & _
    "GetFileName hresult(wstr*);" & _
    "SetTitle hresult(wstr);" & _
    "SetOkButtonLabel hresult(wstr);" & _
    "SetFileNameLabel hresult(wstr);" & _
    "GetResult hresult(ptr*);" & _
    "AddPlace hresult(ptr;dword);" & _
    "SetDefaultExtension hresult(wstr);" & _
    "Close hresult(hresult);" & _
    "SetClientGuid hresult(clsid);" & _
    "ClearClientData hresult();" & _
    "SetFilter hresult(ptr);" & _
    "GetResults hresult(ptr*);" & _
    "GetSelectedItems hresult(ptr*);"

Global Const $tagIFileDialogCustomize = _
    "EnableOpenDropDown hresult(dword);" & _
    "AddMenu hresult(dword;wstr);" & _
    "AddPushButton hresult(dword;wstr);" & _
    "AddComboBox hresult(dword);" & _
    "AddRadioButtonList hresult(dword);" & _
    "AddCheckButton hresult(dword;wstr;bool);" & _
    "AddEditBox hresult(dword;wstr);" & _
    "AddSeparator hresult(dword);" & _
    "AddText hresult(dword;wstr);" & _
    "SetControlLabel hresult(dword;wstr);" & _
    "GetControlState hresult(dword;dword*);" & _
    "SetControlState hresult(dword;dword);" & _
    "GetEditBoxText hresult(dword;ptr*);" & _
    "SetEditBoxText hresult(dword;wstr);" & _
    "GetCheckButtonState hresult(dword;bool*);" & _
    "SetCheckButtonState hresult(dword;bool);" & _
    "AddControlItem hresult(dword;dword;wstr);" & _
    "RemoveControlItem hresult(dword;dword);" & _
    "RemoveAllControlItems hresult(dword);" & _
    "GetControlItemState hresult(dword;dword;dword*);" & _
    "SetControlItemState hresult(dword;dword;dword);" & _
    "GetSelectedControlItem hresult(dword;dword*);" & _
    "SetSelectedControlItem hresult(dword;dword);" & _
    "StartVisualGroup hresult(dword;wstr);" & _
    "EndVisualGroup hresult();" & _
    "MakeProminent hresult(dword);" & _
    "SetControlItemText hresult(dword;dword;wstr);"

Global Const $tagIShellItemArray = _
    "BindToHandler hresult(ptr;clsid;clsid;ptr*);" & _
    "GetPropertyStore hresult(int;clsid;ptr*);" & _
    "GetPropertyDescriptionList hresult(ptr;clsid;ptr*);" & _
    "GetAttributes hresult(int;int;int*);" & _
    "GetCount hresult(uint*);" & _
    "GetItemAt hresult(uint;ptr*);" & _
    "EnumItems hresult(ptr*);"

Global Const $tagIShellItem = _
    "BindToHandler hresult(ptr;clsid;clsid;ptr*);" & _
    "GetParent hresult(ptr*);" & _
    "GetDisplayName hresult(int;ptr*);" & _
    "GetAttributes hresult(ulong;ulong*);" & _
    "Compare hresult(ptr;ulong;int*);"

Global Const $SIGDN_NORMALDISPLAY                 = 0x00000000
Global Const $SIGDN_PARENTRELATIVEPARSING         = 0x80018001
Global Const $SIGDN_DESKTOPABSOLUTEPARSING        = 0x80028000
Global Const $SIGDN_PARENTRELATIVEEDITING         = 0x80030001
Global Const $SIGDN_DESKTOPABSOLUTEEDITING        = 0x8004C000
Global Const $SIGDN_FILESYSPATH                   = 0x80058000
Global Const $SIGDN_URL                           = 0x80068000
Global Const $SIGDN_PARENTRELATIVEFORADDRESSBAR   = 0x80070001
Global Const $SIGDN_PARENTRELATIVE                = 0x80080001
Global Const $SIGDN_PARENTRELATIVEFORUI           = 0x80090001


; Open Dialog
Local $sFullPath, $iEncoding
Local $aRetOpen = DoFileOpen(0)
If IsArray($aRetOpen) Then
    $sFullPath = $aRetOpen[0]
    $iEncoding = $aRetOpen[1]
    ConsoleWrite("File path and name: " & $sFullPath & @CRLF)
    Switch $iEncoding
        Case 0
            ConsoleWrite("File encoding: Auto-Detect" & @CRLF)
            ConsoleWrite("TODO: need to scan bytes to determine real encoding." & @CRLF)

        Case Else
            ConsoleWrite("File encoding: " & $iEncoding & @CRLF)
    EndSwitch
EndIf

; Save As Dialog
Local $aReturn = DoFileSave(0)
If IsArray($aReturn) Then
    ConsoleWrite("File path and name: " & $aReturn[0] & @CRLF)
    ConsoleWrite("File encoding: " & $aReturn[1] & @CRLF)
EndIf

Func DoFileSave($hParent)
    Local $oFileSave = ObjCreateInterface($sCLSID_FileSaveDialog, $sIID_IFileSaveDialog, $tagIFileSaveDialog)

    Local $pCustomFileSave, $tIID_IFileDialogCustomize  = _WinAPI_GUIDFromString($sIID_IFileDialogCustomize)
    $oFileSave.QueryInterface(DllStructGetPtr($tIID_IFileDialogCustomize), $pCustomFileSave)
    Local $oCustomFileSave = ObjCreateInterface(Ptr($pCustomFileSave), $sIID_IFileDialogCustomize, $tagIFileDialogCustomize)

    Local $sName1 = "Text Documents (*.txt)"
    Local $sSpec1 = "*.txt"
    Local $sName2 = "All Files (*.*)"
    Local $sSpec2 = "*.*"

    Local $tName1 = DllStructCreate("wchar[" & (StringLen($sName1) + 1) & "]")
    DllStructSetData($tName1, 1, $sName1)
    Local $tSpec1 = DllStructCreate("wchar[" & (StringLen($sSpec1) + 1) & "]")
    DllStructSetData($tSpec1, 1, $sSpec1)

    Local $tName2 = DllStructCreate("wchar[" & (StringLen($sName2) + 1) & "]")
    DllStructSetData($tName2, 1, $sName2)
    Local $tSpec2 = DllStructCreate("wchar[" & (StringLen($sSpec2) + 1) & "]")
    DllStructSetData($tSpec2, 1, $sSpec2)

    Local $tFilters = DllStructCreate("ptr;ptr;ptr;ptr")
    DllStructSetData($tFilters, 1, DllStructGetPtr($tName1))
    DllStructSetData($tFilters, 2, DllStructGetPtr($tSpec1))
    DllStructSetData($tFilters, 3, DllStructGetPtr($tName2))
    DllStructSetData($tFilters, 4, DllStructGetPtr($tSpec2))

    $oFileSave.SetFileTypes(2, DllStructGetPtr($tFilters))
    $oFileSave.SetFileTypeIndex(1) ; Set Text Documents (*.txt) as default
    $oFileSave.SetDefaultExtension("txt")

    Local Enum $idVisualGroup = 100, $idCombo, $idItemANSI, $idItemUTF8, $idItemUTF16LE, $idItemUTF16BE, $idItemUTF8BOM

    $oCustomFileSave.StartVisualGroup($idVisualGroup, "&Encoding:")
    $oCustomFileSave.AddComboBox($idCombo)
    $oCustomFileSave.AddControlItem($idCombo, $idItemANSI, "ANSI")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF16LE, "UTF-16 LE")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF16BE, "UTF-16 BE")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF8, "UTF-8")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF8BOM, "UTF-8 with BOM")
    $oCustomFileSave.SetSelectedControlItem($idCombo, $idItemUTF8)
    $oCustomFileSave.EndVisualGroup()

    $oFileSave.Show($hParent)

    Local $pItem, $pDispName = 0, $sFullPath = ""
    
    $oFileSave.GetResult($pItem)

    If @error Or Not $pItem Then
        ConsoleWrite("User cancelled the Save dialog." & @CRLF)
        Return
    EndIf
    
    Local $aReturn[2]

    Local $idSelItem, $iEncode
    $oCustomFileSave.GetSelectedControlItem($idCombo, $idSelItem)
    Switch $idSelItem
        Case $idItemANSI
            $iEncode = $FO_ANSI

        Case $idItemUTF16LE
            $iEncode = $FO_UTF16_LE

        Case $idItemUTF16BE
            $iEncode = $FO_UTF16_BE

        Case $idItemUTF8
            $iEncode = $FO_UTF8_NOBOM

        Case $idItemUTF8BOM
            $iEncode = $FO_UTF8
    EndSwitch

    Local $oItem = ObjCreateInterface(Ptr($pItem), $sIID_IShellItem, $tagIShellItem)
        
    $oItem.GetDisplayName($SIGDN_FILESYSPATH, $pDispName)

    If $pDispName Then
        Local $tDispName = DllStructCreate("wchar[4096]", $pDispName)
        $sFullPath = DllStructGetData($tDispName, 1)
        $aReturn[0] = $sFullPath
        $aReturn[1] = $iEncode
        _WinAPI_CoTaskMemFree($pDispName)
    EndIf

    $oItem = 0
    _WinAPI_FreeMemory($pItem)

    Return $aReturn
EndFunc

Func DoFileOpen($hParent)
    Local $oFileOpen = ObjCreateInterface($sCLSID_FileOpenDialog, $sIID_IFileOpenDialog, $tagIFileOpenDialog)

    Local $pCustomFileOpen, $tIID_IFileDialogCustomize  = _WinAPI_GUIDFromString($sIID_IFileDialogCustomize)
    $oFileOpen.QueryInterface(DllStructGetPtr($tIID_IFileDialogCustomize), $pCustomFileOpen)
    Local $oCustomFileOpen = ObjCreateInterface(Ptr($pCustomFileOpen), $sIID_IFileDialogCustomize, $tagIFileDialogCustomize)

    Local Enum $idVisualGroup = 100, $idCombo, $idAutoDetect, $idItemANSI, $idItemUTF8, $idItemUTF16LE, $idItemUTF16BE, $idItemUTF8BOM

    $oCustomFileOpen.StartVisualGroup($idVisualGroup, "&Encoding:")
    $oCustomFileOpen.AddComboBox($idCombo)
    $oCustomFileOpen.AddControlItem($idCombo, $idAutoDetect, "Auto-Detect")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemANSI, "ANSI")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF16LE, "UTF-16 LE")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF16BE, "UTF-16 BE")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF8, "UTF-8")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF8BOM, "UTF-8 with BOM")
    $oCustomFileOpen.SetSelectedControlItem($idCombo, $idAutoDetect)
    $oCustomFileOpen.EndVisualGroup()

    $oFileOpen.Show($hParent)

    Local $aReturn[2]

    Local $idSelItem, $sSelItem
    $oCustomFileOpen.GetSelectedControlItem($idCombo, $idSelItem)

    Switch $idSelItem
        Case $idAutoDetect
            $iEncode = 0

        Case $idItemANSI
            $iEncode = $FO_ANSI

        Case $idItemUTF16LE
            $iEncode = $FO_UTF16_LE

        Case $idItemUTF16BE
            $iEncode = $FO_UTF16_BE

        Case $idItemUTF8
            $iEncode = $FO_UTF8_NOBOM

        Case $idItemUTF8BOM
            $iEncode = $FO_UTF8
    EndSwitch

    Local $pItemArr, $pItem, $pDispName = 0, $sFullPath = ""
    $oFileOpen.GetResults($pItemArr)

    If @error Or Not $pItemArr Then
        ConsoleWrite("User cancelled the Open dialog." & @CRLF)
        Return
    EndIf

    Local $oItemArr = ObjCreateInterface(Ptr($pItemArr), $sIID_IShellItemArray, $tagIShellItemArray)
    $oItemArr.GetItemAt(0, $pItem)

    Local $oItem = ObjCreateInterface(Ptr($pItem), $sIID_IShellItem, $tagIShellItem)

    ; This will now properly request and retrieve the absolute file path
    $oItem.GetDisplayName($SIGDN_FILESYSPATH, $pDispName)

    If $pDispName Then
        Local $tDispName = DllStructCreate("wchar[4096]", $pDispName)
        $sFullPath = DllStructGetData($tDispName, 1)
        $aReturn[0] = $sFullPath
        $aReturn[1] = $iEncode
        _WinAPI_CoTaskMemFree($pDispName)
    EndIf

    $oItemArr = 0
    $oItem = 0
    _WinAPI_FreeMemory($pItemArr)

    Return $aReturn
EndFunc

 

Posted

Good stuff, :)

Fixed SIGDN_ definitions 👍

Freeing GetDisplayName is also a good pickup. This is absolutely fine - but just a tip,  you can generally make filename buffers 260 characters - which is the value of MAX_PATH.

$oItem.GetDisplayName($SIGDN_FILESYSPATH, $pDispName)

If $pDispName Then
  Local $tDispName = DllStructCreate("wchar[4096]", $pDispName)
  $sFullPath = DllStructGetData($tDispName, 1)
  $aReturn[0] = $sFullPath
  $aReturn[1] = $iEncode
  _WinAPI_CoTaskMemFree($pDispName)
EndIf

This was a brain fart, it can disappear. 

$oItemArr = 0
 $oItem = 0
 _WinAPI_FreeMemory($pItemArr)

Another good call to error check here. 
These methods all return HResult - so the error code will be there rather than in @error.  But I'd imagine just checking for a valid $pItemArr will probably be enough.

$oFileOpen.GetResults($pItemArr)

    If @error Or Not $pItemArr Then
        ConsoleWrite("User cancelled the Open dialog." & @CRLF)
        Return
    EndIf
    
;$iResult =  $oFileOpen.GetResults($pItemArr)
Posted (edited)
35 minutes ago, MattyD said:

you can generally make filename buffers 260 characters - which is the value of MAX_PATH.

Good suggestion and makes sense. I've changed it to 260 characters for both functions now.

Is it worthwhile at all to check a system for LongPathsEnabled setting?

35 minutes ago, MattyD said:

But I'd imagine just checking for a valid $pItemArr will probably be enough.

True, you are right. It does seem to be sufficient. I've made the relevant change for both functions.

Thank you for presenting this idea along with some great working code examples and also for checking over my changes. I appreciate your time. Especially when it comes to error checking on this COM stuff because I am not very familiar with it.

It looks like this custom Open dialog and Save As dialog should give us exactly what is needed for this Notepad clone project.

Edited by WildByDesign
Posted
Quote

you can generally make filename buffers 260 characters

On second thought, we technically should use _WinAPI_StringLenW in this situation (assuming the string is null-terminated)

The method might only allocates enough for the string - and 260 chars will probably overrun this.. 

Posted
4 minutes ago, WildByDesign said:

Is it worthwhile at all to check a system for LongPathsEnabled setting?

I don't think so, by memory the app needs to opt-in to long paths in its manifest. But you may want to check me on this.

Posted

@MattyD Can you please check the Auto-Detect functionality in the Open dialog when you have a moment?

Thank you. I had to get some help from AI with scanning the bytes. My testing so far with it has checked out OK. But it's not an area that I am familiar with. The Auto-Detect triggers the _DetectFileEncoding function. The idea is to replicate how Notepad handles it exactly.

#include <WinAPI.au3>
#include <GuiConstants.au3>

Global Const $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = Ptr(-4)
DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "int_ptr", $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)

Global Const $sCLSID_FileSaveDialog = "{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}"
Global Const $sIID_IFileSaveDialog = "{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}"
Global Const $sIID_IFileDialogCustomize = "{e6fdd21a-163f-4975-9c8c-a69f1ba37034}"
Global Const $sIID_IShellItem = "{43826d1e-e718-42ee-bc55-a1e261c37bfe}"

Global Const $sCLSID_FileOpenDialog = "{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}"
Global Const $sIID_IFileOpenDialog = "{d57c7288-d4ad-4768-be02-9d969532d960}"
Global Const $sIID_IShellItemArray = "{b63ea76d-1f85-456f-a19c-48159efa858b}"

Global Const $tagIFileSaveDialog = _
    "Show hresult(hwnd);" & _
    "SetFileTypes hresult(uint;ptr);" & _
    "SetFileTypeIndex hresult(uint);" & _
    "GetFileTypeIndex hresult(uint*);" & _
    "Advise hresult(ptr;dword*);" & _
    "Unadvise hresult(dword);" & _
    "SetOptions hresult(dword);" & _
    "GetOptions hresult(dword*);" & _
    "SetDefaultFolder hresult(ptr);" & _
    "SetFolder hresult(ptr);" & _
    "GetFolder hresult(ptr*);" & _
    "GetCurrentSelection hresult(ptr*);" & _
    "SetFileName hresult(wstr);" & _
    "GetFileName hresult(wstr*);" & _
    "SetTitle hresult(wstr);" & _
    "SetOkButtonLabel hresult(wstr);" & _
    "SetFileNameLabel hresult(wstr);" & _
    "GetResult hresult(ptr*);" & _
    "AddPlace hresult(ptr;dword);" & _
    "SetDefaultExtension hresult(wstr);" & _
    "Close hresult(hresult);" & _
    "SetClientGuid hresult(clsid);" & _
    "ClearClientData hresult();" & _
    "SetFilter hresult(ptr);" & _
    "GetResults hresult(ptr*);" & _
    "GetSelectedItems hresult(ptr*);"

Global Const $tagIFileOpenDialog = _
    "Show hresult(hwnd);" & _
    "SetFileTypes hresult(uint;ptr);" & _
    "SetFileTypeIndex hresult(uint);" & _
    "GetFileTypeIndex hresult(uint*);" & _
    "Advise hresult(ptr;dword*);" & _
    "Unadvise hresult(dword);" & _
    "SetOptions hresult(dword);" & _
    "GetOptions hresult(dword*);" & _
    "SetDefaultFolder hresult(ptr);" & _
    "SetFolder hresult(ptr);" & _
    "GetFolder hresult(ptr*);" & _
    "GetCurrentSelection hresult(ptr*);" & _
    "SetFileName hresult(wstr);" & _
    "GetFileName hresult(wstr*);" & _
    "SetTitle hresult(wstr);" & _
    "SetOkButtonLabel hresult(wstr);" & _
    "SetFileNameLabel hresult(wstr);" & _
    "GetResult hresult(ptr*);" & _
    "AddPlace hresult(ptr;dword);" & _
    "SetDefaultExtension hresult(wstr);" & _
    "Close hresult(hresult);" & _
    "SetClientGuid hresult(clsid);" & _
    "ClearClientData hresult();" & _
    "SetFilter hresult(ptr);" & _
    "GetResults hresult(ptr*);" & _
    "GetSelectedItems hresult(ptr*);"

Global Const $tagIFileDialogCustomize = _
    "EnableOpenDropDown hresult(dword);" & _
    "AddMenu hresult(dword;wstr);" & _
    "AddPushButton hresult(dword;wstr);" & _
    "AddComboBox hresult(dword);" & _
    "AddRadioButtonList hresult(dword);" & _
    "AddCheckButton hresult(dword;wstr;bool);" & _
    "AddEditBox hresult(dword;wstr);" & _
    "AddSeparator hresult(dword);" & _
    "AddText hresult(dword;wstr);" & _
    "SetControlLabel hresult(dword;wstr);" & _
    "GetControlState hresult(dword;dword*);" & _
    "SetControlState hresult(dword;dword);" & _
    "GetEditBoxText hresult(dword;ptr*);" & _
    "SetEditBoxText hresult(dword;wstr);" & _
    "GetCheckButtonState hresult(dword;bool*);" & _
    "SetCheckButtonState hresult(dword;bool);" & _
    "AddControlItem hresult(dword;dword;wstr);" & _
    "RemoveControlItem hresult(dword;dword);" & _
    "RemoveAllControlItems hresult(dword);" & _
    "GetControlItemState hresult(dword;dword;dword*);" & _
    "SetControlItemState hresult(dword;dword;dword);" & _
    "GetSelectedControlItem hresult(dword;dword*);" & _
    "SetSelectedControlItem hresult(dword;dword);" & _
    "StartVisualGroup hresult(dword;wstr);" & _
    "EndVisualGroup hresult();" & _
    "MakeProminent hresult(dword);" & _
    "SetControlItemText hresult(dword;dword;wstr);"

Global Const $tagIShellItemArray = _
    "BindToHandler hresult(ptr;clsid;clsid;ptr*);" & _
    "GetPropertyStore hresult(int;clsid;ptr*);" & _
    "GetPropertyDescriptionList hresult(ptr;clsid;ptr*);" & _
    "GetAttributes hresult(int;int;int*);" & _
    "GetCount hresult(uint*);" & _
    "GetItemAt hresult(uint;ptr*);" & _
    "EnumItems hresult(ptr*);"

Global Const $tagIShellItem = _
    "BindToHandler hresult(ptr;clsid;clsid;ptr*);" & _
    "GetParent hresult(ptr*);" & _
    "GetDisplayName hresult(int;ptr*);" & _
    "GetAttributes hresult(ulong;ulong*);" & _
    "Compare hresult(ptr;ulong;int*);"

Global Const $SIGDN_NORMALDISPLAY                 = 0x00000000
Global Const $SIGDN_PARENTRELATIVEPARSING         = 0x80018001
Global Const $SIGDN_DESKTOPABSOLUTEPARSING        = 0x80028000
Global Const $SIGDN_PARENTRELATIVEEDITING         = 0x80030001
Global Const $SIGDN_DESKTOPABSOLUTEEDITING        = 0x8004C000
Global Const $SIGDN_FILESYSPATH                   = 0x80058000
Global Const $SIGDN_URL                           = 0x80068000
Global Const $SIGDN_PARENTRELATIVEFORADDRESSBAR   = 0x80070001
Global Const $SIGDN_PARENTRELATIVE                = 0x80080001
Global Const $SIGDN_PARENTRELATIVEFORUI           = 0x80090001


; Open Dialog
Local $sFullPath, $iEncoding
Local $aRetOpen = DoFileOpen(0)
If IsArray($aRetOpen) Then
    $sFullPath = $aRetOpen[0]
    $iEncoding = $aRetOpen[1]
    ConsoleWrite("File path and name: " & $sFullPath & @CRLF)
    ConsoleWrite("File encoding: " & $iEncoding & @CRLF)
EndIf

; Save As Dialog
Local $aReturn = DoFileSave(0)
If IsArray($aReturn) Then
    ConsoleWrite("File path and name: " & $aReturn[0] & @CRLF)
    ConsoleWrite("File encoding: " & $aReturn[1] & @CRLF)
EndIf

Func DoFileSave($hParent)
    Local $oFileSave = ObjCreateInterface($sCLSID_FileSaveDialog, $sIID_IFileSaveDialog, $tagIFileSaveDialog)

    Local $pCustomFileSave, $tIID_IFileDialogCustomize  = _WinAPI_GUIDFromString($sIID_IFileDialogCustomize)
    $oFileSave.QueryInterface(DllStructGetPtr($tIID_IFileDialogCustomize), $pCustomFileSave)
    Local $oCustomFileSave = ObjCreateInterface(Ptr($pCustomFileSave), $sIID_IFileDialogCustomize, $tagIFileDialogCustomize)

    Local $sName1 = "Text Documents (*.txt)"
    Local $sSpec1 = "*.txt"
    Local $sName2 = "All Files (*.*)"
    Local $sSpec2 = "*.*"

    Local $tName1 = DllStructCreate("wchar[" & (StringLen($sName1) + 1) & "]")
    DllStructSetData($tName1, 1, $sName1)
    Local $tSpec1 = DllStructCreate("wchar[" & (StringLen($sSpec1) + 1) & "]")
    DllStructSetData($tSpec1, 1, $sSpec1)

    Local $tName2 = DllStructCreate("wchar[" & (StringLen($sName2) + 1) & "]")
    DllStructSetData($tName2, 1, $sName2)
    Local $tSpec2 = DllStructCreate("wchar[" & (StringLen($sSpec2) + 1) & "]")
    DllStructSetData($tSpec2, 1, $sSpec2)

    Local $tFilters = DllStructCreate("ptr;ptr;ptr;ptr")
    DllStructSetData($tFilters, 1, DllStructGetPtr($tName1))
    DllStructSetData($tFilters, 2, DllStructGetPtr($tSpec1))
    DllStructSetData($tFilters, 3, DllStructGetPtr($tName2))
    DllStructSetData($tFilters, 4, DllStructGetPtr($tSpec2))

    $oFileSave.SetFileTypes(2, DllStructGetPtr($tFilters))
    $oFileSave.SetFileTypeIndex(1) ; Set Text Documents (*.txt) as default
    $oFileSave.SetDefaultExtension("txt")

    Local Enum $idVisualGroup = 100, $idCombo, $idItemANSI, $idItemUTF8, $idItemUTF16LE, $idItemUTF16BE, $idItemUTF8BOM

    $oCustomFileSave.StartVisualGroup($idVisualGroup, "&Encoding:")
    $oCustomFileSave.AddComboBox($idCombo)
    $oCustomFileSave.AddControlItem($idCombo, $idItemANSI, "ANSI")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF16LE, "UTF-16 LE")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF16BE, "UTF-16 BE")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF8, "UTF-8")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF8BOM, "UTF-8 with BOM")
    $oCustomFileSave.SetSelectedControlItem($idCombo, $idItemUTF8)
    $oCustomFileSave.EndVisualGroup()

    $oFileSave.Show($hParent)

    Local $pItem, $pDispName = 0, $sFullPath = ""
    
    $oFileSave.GetResult($pItem)

    If Not $pItem Then
        ConsoleWrite("User cancelled the Save dialog." & @CRLF)
        Return
    EndIf
    
    Local $aReturn[2]

    Local $idSelItem, $iEncode
    $oCustomFileSave.GetSelectedControlItem($idCombo, $idSelItem)
    Switch $idSelItem
        Case $idItemANSI
            $iEncode = $FO_ANSI

        Case $idItemUTF16LE
            $iEncode = $FO_UTF16_LE

        Case $idItemUTF16BE
            $iEncode = $FO_UTF16_BE

        Case $idItemUTF8
            $iEncode = $FO_UTF8_NOBOM

        Case $idItemUTF8BOM
            $iEncode = $FO_UTF8
    EndSwitch

    Local $oItem = ObjCreateInterface(Ptr($pItem), $sIID_IShellItem, $tagIShellItem)
        
    $oItem.GetDisplayName($SIGDN_FILESYSPATH, $pDispName)

    If $pDispName Then
        Local $tDispName = DllStructCreate("wchar[260]", $pDispName)
        $sFullPath = DllStructGetData($tDispName, 1)
        $aReturn[0] = $sFullPath
        $aReturn[1] = $iEncode
        _WinAPI_CoTaskMemFree($pDispName)
    EndIf

    $oItem = 0
    _WinAPI_FreeMemory($pItem)

    Return $aReturn
EndFunc

Func DoFileOpen($hParent)
    Local $oFileOpen = ObjCreateInterface($sCLSID_FileOpenDialog, $sIID_IFileOpenDialog, $tagIFileOpenDialog)

    Local $pCustomFileOpen, $tIID_IFileDialogCustomize  = _WinAPI_GUIDFromString($sIID_IFileDialogCustomize)
    $oFileOpen.QueryInterface(DllStructGetPtr($tIID_IFileDialogCustomize), $pCustomFileOpen)
    Local $oCustomFileOpen = ObjCreateInterface(Ptr($pCustomFileOpen), $sIID_IFileDialogCustomize, $tagIFileDialogCustomize)

    Local Enum $idVisualGroup = 100, $idCombo, $idAutoDetect, $idItemANSI, $idItemUTF8, $idItemUTF16LE, $idItemUTF16BE, $idItemUTF8BOM

    $oCustomFileOpen.StartVisualGroup($idVisualGroup, "&Encoding:")
    $oCustomFileOpen.AddComboBox($idCombo)
    $oCustomFileOpen.AddControlItem($idCombo, $idAutoDetect, "Auto-Detect")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemANSI, "ANSI")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF16LE, "UTF-16 LE")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF16BE, "UTF-16 BE")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF8, "UTF-8")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF8BOM, "UTF-8 with BOM")
    $oCustomFileOpen.SetSelectedControlItem($idCombo, $idAutoDetect)
    $oCustomFileOpen.EndVisualGroup()

    $oFileOpen.Show($hParent)

    Local $aReturn[2]

    Local $idSelItem, $sSelItem
    $oCustomFileOpen.GetSelectedControlItem($idCombo, $idSelItem)

    Switch $idSelItem
        Case $idAutoDetect
            $iEncode = 0

        Case $idItemANSI
            $iEncode = $FO_ANSI

        Case $idItemUTF16LE
            $iEncode = $FO_UTF16_LE

        Case $idItemUTF16BE
            $iEncode = $FO_UTF16_BE

        Case $idItemUTF8
            $iEncode = $FO_UTF8_NOBOM

        Case $idItemUTF8BOM
            $iEncode = $FO_UTF8
    EndSwitch

    Local $pItemArr, $pItem, $pDispName = 0, $sFullPath = ""
    $oFileOpen.GetResults($pItemArr)

    If Not $pItemArr Then
        ConsoleWrite("User cancelled the Open dialog." & @CRLF)
        Return
    EndIf

    Local $oItemArr = ObjCreateInterface(Ptr($pItemArr), $sIID_IShellItemArray, $tagIShellItemArray)
    $oItemArr.GetItemAt(0, $pItem)

    Local $oItem = ObjCreateInterface(Ptr($pItem), $sIID_IShellItem, $tagIShellItem)

    $oItem.GetDisplayName($SIGDN_FILESYSPATH, $pDispName)

    If $pDispName Then
        Local $tDispName = DllStructCreate("wchar[260]", $pDispName)
        $sFullPath = DllStructGetData($tDispName, 1)
        $aReturn[0] = $sFullPath
        If Not $iEncode Then
            ConsoleWrite("user kept auto detect" & @CRLF)
            $iEncode = _DetectFileEncoding($sFullPath)
        EndIf
        $aReturn[1] = $iEncode
        _WinAPI_CoTaskMemFree($pDispName)
    EndIf

    $oItemArr = 0
    $oItem = 0
    _WinAPI_FreeMemory($pItemArr)

    Return $aReturn
EndFunc

Func _DetectFileEncoding($sFilePath)
    Local $hFile = FileOpen($sFilePath, 16)
    If $hFile = -1 Then Return $FO_ANSI

    Local $bData = FileRead($hFile, 4096)
    FileClose($hFile)

    Local $iLen = BinaryLen($bData)
    If $iLen = 0 Then Return $FO_ANSI

    ; Check for Byte Order Marks (BOM)
    If $iLen >= 3 And BinaryMid($bData, 1, 3) == "0xEFBBBF" Then Return $FO_UTF8
    If $iLen >= 2 And BinaryMid($bData, 1, 2) == "0xFFFE" Then Return $FO_UTF16_LE
    If $iLen >= 2 And BinaryMid($bData, 1, 2) == "0xFEFF" Then Return $FO_UTF16_BE

    ; Heuristic Check
    Local $i = 1
    Local $bHasHighBit = False
    
    While $i <= $iLen
        Local $iByte = Number(BinaryMid($bData, $i, 1))
        
        If $iByte < 128 Then
            $i += 1
        Else
            $bHasHighBit = True
            Local $iBytesExpected = 0
            
            If BitAND($iByte, 0xE0) = 0xC0 Then
                $iBytesExpected = 1
            ElseIf BitAND($iByte, 0xF0) = 0xE0 Then
                $iBytesExpected = 2
            ElseIf BitAND($iByte, 0xF8) = 0xF0 Then
                $iBytesExpected = 3
            Else
                Return $FO_ANSI
            EndIf
            
            If $i + $iBytesExpected > $iLen Then Return $FO_ANSI
            
            For $j = 1 To $iBytesExpected
                $i += 1
                Local $iContByte = Number(BinaryMid($bData, $i, 1))
                If BitAND($iContByte, 0xC0) <> 0x80 Then Return $FO_ANSI
            Next
            $i += 1
        EndIf
    WEnd
    
    ; No BOM and no violation -> UTF-8 without BOM ($FO_UTF8_NOBOM)
    Return $FO_UTF8_NOBOM
EndFunc

 

Posted
11 hours ago, MattyD said:

we technically should use _WinAPI_StringLenW in this situation (assuming the string is null-terminated)

Sorry, I forgot to mention that I'm not entirely sure how to implement this. From my limited knowledge, I don't think that we even have the string yet at the point where we call DllStructCreate. This is very much above my head and you will understand it better than I do, of course. :)

From DoFileOpen():

If $pDispName Then
    Local $tDispName = DllStructCreate("wchar[260]", $pDispName)
    $sFullPath = DllStructGetData($tDispName, 1)
    $aReturn[0] = $sFullPath
    If Not $iEncode Then
        $iEncode = _DetectFileEncoding($sFullPath)
    EndIf
    $aReturn[1] = $iEncode
    _WinAPI_CoTaskMemFree($pDispName)
EndIf

From DoFileSave():

If $pDispName Then
    Local $tDispName = DllStructCreate("wchar[260]", $pDispName)
    $sFullPath = DllStructGetData($tDispName, 1)
    $aReturn[0] = $sFullPath
    $aReturn[1] = $iEncode
    _WinAPI_CoTaskMemFree($pDispName)
EndIf

I believe that those are the two relevant sections that could potentially use _WinAPI_StringLenW. If you have time, could you please share how that would be implemented?

  • Solution
Posted (edited)

No worries. I'll answer the second question first, and look at the heuristics tomorrow.

1 hour ago, WildByDesign said:

I don't think that we even have the string yet at the point

Yep, we certainly do. GetDisplayName creates the string, and to extract it we need to know:

  • Where it is - We're told its at $pDisplayName.
  • How big it is - We do this using StringLenW. It finds the first null character after  $pDisplayName.
Local $pDispName
$oItem.GetDisplayName($SIGDN_FILESYSPATH, $pDispName)
; GetDisplayName creates & fills a buffer. It puts the address in $pDispName, but we still don't know how big it is.

If $pDispName Then

    ; _WinAPI_StringLenW($pDispName) starts at $pDispName, and will keep counting characters until it finds a NULL (ChrW(0)).
    ; It then returns that count.
    
    Local $tDispName = DllStructCreate("wchar[" & _WinAPI_StringLenW($pDispName) &  "]", $pDispName)
    $sFullPath = DllStructGetData($tDispName, 1)
    
    ;its our responsibility to free that buffer when we're done with it.
    _WinAPI_CoTaskMemFree($pDispName)
EndIf
Edited by MattyD
wording
Posted
On 10/5/2026 at 9:12 PM, WildByDesign said:

scanning the bytes

Not too bad for the UTF8 check - The main issue is this line:

If $i + $iBytesExpected > $iLen Then Return $FO_ANSI

We haven't necessarily read in the whole file, so this is problematic if the file is > 4096 bytes.

Here's something to test the first 96k of the file.

Func IsUTF8($hFile)
    FileSetPos($hFile, 0, $FILE_BEGIN)
    Local $dChunk, $dByte, $iContBytes
    Local $iChunkSize = 4096, $iMaxChunks = 24 ;Check first 96k

    For $i = 1 To $iMaxChunks
        $dChunk = FileRead($hFile, $iChunkSize)
        If Not BinaryLen($dChunk) Then ExitLoop

        For $j = 1 To BinaryLen($dChunk)
            $dByte = BinaryMid($dChunk, $i, 1)

            If $iContBytes Then
                ;Test for: 10nn nnnn
                If BitAND($dByte, 0xC0) <> 0x80 Then Return False
                $iContBytes -= 1

            ElseIf BitAND($dByte, 0x80) Then

                ; Test for: 110n nnnn
                If BitAND($dByte, 0xE0) = 0xC0 Then
                    $iContBytes = 1

                ; Test for: 1110 nnnn
                ElseIf BitAND($dByte, 0xF0) = 0xE0 Then
                    $iContBytes = 2

                ; Test for: 1111 0nnn
                ElseIf BitAND($dByte, 0xF8) = 0xF0 Then
                    $iContBytes = 3

                Else
                    Return False
                EndIf
            EndIf
        Next
    Next

    Return True
EndFunc

 

Posted
On 10/6/2026 at 4:00 PM, MattyD said:

Here's something to test the first 96k of the file.

Thanks for this updated code. It took me some time to learn more about the various encoding types before I went ahead and put it all together.

If you have some time, please test the Open dialog with the Auto-Detect (default) encoding and ensure that it is working correctly. Thanks. :)

#include <WinAPI.au3>
#include <GuiConstants.au3>

Global Const $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = Ptr(-4)
DllCall("User32.dll", "bool", "SetProcessDpiAwarenessContext", "int_ptr", $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)

Global Const $sCLSID_FileSaveDialog = "{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}"
Global Const $sIID_IFileSaveDialog = "{84BCCD23-5FDE-4CDB-AEA4-AF64B83D78AB}"
Global Const $sIID_IFileDialogCustomize = "{e6fdd21a-163f-4975-9c8c-a69f1ba37034}"
Global Const $sIID_IShellItem = "{43826d1e-e718-42ee-bc55-a1e261c37bfe}"

Global Const $sCLSID_FileOpenDialog = "{DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7}"
Global Const $sIID_IFileOpenDialog = "{d57c7288-d4ad-4768-be02-9d969532d960}"
Global Const $sIID_IShellItemArray = "{b63ea76d-1f85-456f-a19c-48159efa858b}"

Global Const $tagIFileSaveDialog = _
    "Show hresult(hwnd);" & _
    "SetFileTypes hresult(uint;ptr);" & _
    "SetFileTypeIndex hresult(uint);" & _
    "GetFileTypeIndex hresult(uint*);" & _
    "Advise hresult(ptr;dword*);" & _
    "Unadvise hresult(dword);" & _
    "SetOptions hresult(dword);" & _
    "GetOptions hresult(dword*);" & _
    "SetDefaultFolder hresult(ptr);" & _
    "SetFolder hresult(ptr);" & _
    "GetFolder hresult(ptr*);" & _
    "GetCurrentSelection hresult(ptr*);" & _
    "SetFileName hresult(wstr);" & _
    "GetFileName hresult(wstr*);" & _
    "SetTitle hresult(wstr);" & _
    "SetOkButtonLabel hresult(wstr);" & _
    "SetFileNameLabel hresult(wstr);" & _
    "GetResult hresult(ptr*);" & _
    "AddPlace hresult(ptr;dword);" & _
    "SetDefaultExtension hresult(wstr);" & _
    "Close hresult(hresult);" & _
    "SetClientGuid hresult(clsid);" & _
    "ClearClientData hresult();" & _
    "SetFilter hresult(ptr);" & _
    "GetResults hresult(ptr*);" & _
    "GetSelectedItems hresult(ptr*);"

Global Const $tagIFileOpenDialog = _
    "Show hresult(hwnd);" & _
    "SetFileTypes hresult(uint;ptr);" & _
    "SetFileTypeIndex hresult(uint);" & _
    "GetFileTypeIndex hresult(uint*);" & _
    "Advise hresult(ptr;dword*);" & _
    "Unadvise hresult(dword);" & _
    "SetOptions hresult(dword);" & _
    "GetOptions hresult(dword*);" & _
    "SetDefaultFolder hresult(ptr);" & _
    "SetFolder hresult(ptr);" & _
    "GetFolder hresult(ptr*);" & _
    "GetCurrentSelection hresult(ptr*);" & _
    "SetFileName hresult(wstr);" & _
    "GetFileName hresult(wstr*);" & _
    "SetTitle hresult(wstr);" & _
    "SetOkButtonLabel hresult(wstr);" & _
    "SetFileNameLabel hresult(wstr);" & _
    "GetResult hresult(ptr*);" & _
    "AddPlace hresult(ptr;dword);" & _
    "SetDefaultExtension hresult(wstr);" & _
    "Close hresult(hresult);" & _
    "SetClientGuid hresult(clsid);" & _
    "ClearClientData hresult();" & _
    "SetFilter hresult(ptr);" & _
    "GetResults hresult(ptr*);" & _
    "GetSelectedItems hresult(ptr*);"

Global Const $tagIFileDialogCustomize = _
    "EnableOpenDropDown hresult(dword);" & _
    "AddMenu hresult(dword;wstr);" & _
    "AddPushButton hresult(dword;wstr);" & _
    "AddComboBox hresult(dword);" & _
    "AddRadioButtonList hresult(dword);" & _
    "AddCheckButton hresult(dword;wstr;bool);" & _
    "AddEditBox hresult(dword;wstr);" & _
    "AddSeparator hresult(dword);" & _
    "AddText hresult(dword;wstr);" & _
    "SetControlLabel hresult(dword;wstr);" & _
    "GetControlState hresult(dword;dword*);" & _
    "SetControlState hresult(dword;dword);" & _
    "GetEditBoxText hresult(dword;ptr*);" & _
    "SetEditBoxText hresult(dword;wstr);" & _
    "GetCheckButtonState hresult(dword;bool*);" & _
    "SetCheckButtonState hresult(dword;bool);" & _
    "AddControlItem hresult(dword;dword;wstr);" & _
    "RemoveControlItem hresult(dword;dword);" & _
    "RemoveAllControlItems hresult(dword);" & _
    "GetControlItemState hresult(dword;dword;dword*);" & _
    "SetControlItemState hresult(dword;dword;dword);" & _
    "GetSelectedControlItem hresult(dword;dword*);" & _
    "SetSelectedControlItem hresult(dword;dword);" & _
    "StartVisualGroup hresult(dword;wstr);" & _
    "EndVisualGroup hresult();" & _
    "MakeProminent hresult(dword);" & _
    "SetControlItemText hresult(dword;dword;wstr);"

Global Const $tagIShellItemArray = _
    "BindToHandler hresult(ptr;clsid;clsid;ptr*);" & _
    "GetPropertyStore hresult(int;clsid;ptr*);" & _
    "GetPropertyDescriptionList hresult(ptr;clsid;ptr*);" & _
    "GetAttributes hresult(int;int;int*);" & _
    "GetCount hresult(uint*);" & _
    "GetItemAt hresult(uint;ptr*);" & _
    "EnumItems hresult(ptr*);"

Global Const $tagIShellItem = _
    "BindToHandler hresult(ptr;clsid;clsid;ptr*);" & _
    "GetParent hresult(ptr*);" & _
    "GetDisplayName hresult(int;ptr*);" & _
    "GetAttributes hresult(ulong;ulong*);" & _
    "Compare hresult(ptr;ulong;int*);"

Global Const $SIGDN_NORMALDISPLAY                 = 0x00000000
Global Const $SIGDN_PARENTRELATIVEPARSING         = 0x80018001
Global Const $SIGDN_DESKTOPABSOLUTEPARSING        = 0x80028000
Global Const $SIGDN_PARENTRELATIVEEDITING         = 0x80030001
Global Const $SIGDN_DESKTOPABSOLUTEEDITING        = 0x8004C000
Global Const $SIGDN_FILESYSPATH                   = 0x80058000
Global Const $SIGDN_URL                           = 0x80068000
Global Const $SIGDN_PARENTRELATIVEFORADDRESSBAR   = 0x80070001
Global Const $SIGDN_PARENTRELATIVE                = 0x80080001
Global Const $SIGDN_PARENTRELATIVEFORUI           = 0x80090001


; Open Dialog
Local $sFullPath, $iEncoding
Local $aRetOpen = _DoFileOpen(0)
If IsArray($aRetOpen) Then
    $sFullPath = $aRetOpen[0]
    $iEncoding = $aRetOpen[1]
    ConsoleWrite("File path and name: " & $sFullPath & @CRLF)
    ConsoleWrite("File encoding: " & $iEncoding & @CRLF)
EndIf


; Save As Dialog
;Local $aReturn = _DoFileSave(0)
;If IsArray($aReturn) Then
;    ConsoleWrite("File path and name: " & $aReturn[0] & @CRLF)
;    ConsoleWrite("File encoding: " & $aReturn[1] & @CRLF)
;EndIf

Func _DoFileSave($hParent)
    Local $oFileSave = ObjCreateInterface($sCLSID_FileSaveDialog, $sIID_IFileSaveDialog, $tagIFileSaveDialog)

    Local $pCustomFileSave, $tIID_IFileDialogCustomize  = _WinAPI_GUIDFromString($sIID_IFileDialogCustomize)
    $oFileSave.QueryInterface(DllStructGetPtr($tIID_IFileDialogCustomize), $pCustomFileSave)
    Local $oCustomFileSave = ObjCreateInterface(Ptr($pCustomFileSave), $sIID_IFileDialogCustomize, $tagIFileDialogCustomize)

    Local $sName1 = "Text Documents (*.txt)"
    Local $sSpec1 = "*.txt"
    Local $sName2 = "All Files (*.*)"
    Local $sSpec2 = "*.*"

    Local $tName1 = DllStructCreate("wchar[" & (StringLen($sName1) + 1) & "]")
    DllStructSetData($tName1, 1, $sName1)
    Local $tSpec1 = DllStructCreate("wchar[" & (StringLen($sSpec1) + 1) & "]")
    DllStructSetData($tSpec1, 1, $sSpec1)

    Local $tName2 = DllStructCreate("wchar[" & (StringLen($sName2) + 1) & "]")
    DllStructSetData($tName2, 1, $sName2)
    Local $tSpec2 = DllStructCreate("wchar[" & (StringLen($sSpec2) + 1) & "]")
    DllStructSetData($tSpec2, 1, $sSpec2)

    Local $tFilters = DllStructCreate("ptr;ptr;ptr;ptr")
    DllStructSetData($tFilters, 1, DllStructGetPtr($tName1))
    DllStructSetData($tFilters, 2, DllStructGetPtr($tSpec1))
    DllStructSetData($tFilters, 3, DllStructGetPtr($tName2))
    DllStructSetData($tFilters, 4, DllStructGetPtr($tSpec2))

    $oFileSave.SetFileTypes(2, DllStructGetPtr($tFilters))
    $oFileSave.SetFileTypeIndex(1) ; Set Text Documents (*.txt) as default
    $oFileSave.SetDefaultExtension("txt")

    Local Enum $idVisualGroup = 100, $idCombo, $idItemANSI, $idItemUTF8, $idItemUTF16LE, $idItemUTF16BE, $idItemUTF8BOM

    $oCustomFileSave.StartVisualGroup($idVisualGroup, "&Encoding:")
    $oCustomFileSave.AddComboBox($idCombo)
    $oCustomFileSave.AddControlItem($idCombo, $idItemANSI, "ANSI")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF16LE, "UTF-16 LE")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF16BE, "UTF-16 BE")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF8, "UTF-8")
    $oCustomFileSave.AddControlItem($idCombo, $idItemUTF8BOM, "UTF-8 with BOM")
    $oCustomFileSave.SetSelectedControlItem($idCombo, $idItemUTF8)
    $oCustomFileSave.EndVisualGroup()

    $oFileSave.Show($hParent)

    Local $pItem, $pDispName = 0, $sFullPath = ""
    
    $oFileSave.GetResult($pItem)

    If Not $pItem Then
        ConsoleWrite("User cancelled the Save dialog." & @CRLF)
        Return
    EndIf
    
    Local $aReturn[2]

    Local $idSelItem, $iEncode
    $oCustomFileSave.GetSelectedControlItem($idCombo, $idSelItem)
    Switch $idSelItem
        Case $idItemANSI
            $iEncode = $FO_ANSI

        Case $idItemUTF16LE
            $iEncode = $FO_UTF16_LE

        Case $idItemUTF16BE
            $iEncode = $FO_UTF16_BE

        Case $idItemUTF8
            $iEncode = $FO_UTF8_NOBOM

        Case $idItemUTF8BOM
            $iEncode = $FO_UTF8
    EndSwitch

    Local $oItem = ObjCreateInterface(Ptr($pItem), $sIID_IShellItem, $tagIShellItem)
        
    $oItem.GetDisplayName($SIGDN_FILESYSPATH, $pDispName)

    If $pDispName Then
        Local $tDispName = DllStructCreate("wchar[" & _WinAPI_StringLenW($pDispName) &  "]", $pDispName)
        $sFullPath = DllStructGetData($tDispName, 1)
        $aReturn[0] = $sFullPath
        $aReturn[1] = $iEncode
        _WinAPI_CoTaskMemFree($pDispName)
    EndIf

    $oItem = 0
    _WinAPI_FreeMemory($pItem)

    Return $aReturn
EndFunc   ;==>_DoFileSave

Func _DoFileOpen($hParent)
    Local $oFileOpen = ObjCreateInterface($sCLSID_FileOpenDialog, $sIID_IFileOpenDialog, $tagIFileOpenDialog)

    Local $pCustomFileOpen, $tIID_IFileDialogCustomize  = _WinAPI_GUIDFromString($sIID_IFileDialogCustomize)
    $oFileOpen.QueryInterface(DllStructGetPtr($tIID_IFileDialogCustomize), $pCustomFileOpen)
    Local $oCustomFileOpen = ObjCreateInterface(Ptr($pCustomFileOpen), $sIID_IFileDialogCustomize, $tagIFileDialogCustomize)

    Local Enum $idVisualGroup = 100, $idCombo, $idAutoDetect, $idItemANSI, $idItemUTF8, $idItemUTF16LE, $idItemUTF16BE, $idItemUTF8BOM

    $oCustomFileOpen.StartVisualGroup($idVisualGroup, "&Encoding:")
    $oCustomFileOpen.AddComboBox($idCombo)
    $oCustomFileOpen.AddControlItem($idCombo, $idAutoDetect, "Auto-Detect")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemANSI, "ANSI")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF16LE, "UTF-16 LE")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF16BE, "UTF-16 BE")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF8, "UTF-8")
    $oCustomFileOpen.AddControlItem($idCombo, $idItemUTF8BOM, "UTF-8 with BOM")
    $oCustomFileOpen.SetSelectedControlItem($idCombo, $idAutoDetect)
    $oCustomFileOpen.EndVisualGroup()

    $oFileOpen.Show($hParent)

    Local $aReturn[2]

    Local $idSelItem, $sSelItem
    $oCustomFileOpen.GetSelectedControlItem($idCombo, $idSelItem)

    Switch $idSelItem
        Case $idAutoDetect
            $iEncode = 0

        Case $idItemANSI
            $iEncode = $FO_ANSI

        Case $idItemUTF16LE
            $iEncode = $FO_UTF16_LE

        Case $idItemUTF16BE
            $iEncode = $FO_UTF16_BE

        Case $idItemUTF8
            $iEncode = $FO_UTF8_NOBOM

        Case $idItemUTF8BOM
            $iEncode = $FO_UTF8
    EndSwitch

    Local $pItemArr, $pItem, $pDispName = 0, $sFullPath = ""
    $oFileOpen.GetResults($pItemArr)

    If Not $pItemArr Then
        ConsoleWrite("User cancelled the Open dialog." & @CRLF)
        Return
    EndIf

    Local $oItemArr = ObjCreateInterface(Ptr($pItemArr), $sIID_IShellItemArray, $tagIShellItemArray)
    $oItemArr.GetItemAt(0, $pItem)

    Local $oItem = ObjCreateInterface(Ptr($pItem), $sIID_IShellItem, $tagIShellItem)

    $oItem.GetDisplayName($SIGDN_FILESYSPATH, $pDispName)

    If $pDispName Then
        Local $tDispName = DllStructCreate("wchar[" & _WinAPI_StringLenW($pDispName) &  "]", $pDispName)
        $sFullPath = DllStructGetData($tDispName, 1)
        $aReturn[0] = $sFullPath
        If Not $iEncode Then
            Local $hFile = FileOpen($sFullPath, 16)
            If $hFile = -1 Then
                $iEncode = $FO_ANSI
            Else
                $iEncode = _GetFileEncodingType($hFile)
                ConsoleWrite("Auto-detected encoding: " & $iEncode & @CRLF)
                FileClose($hFile)
            EndIf
        EndIf
        $aReturn[1] = $iEncode
        _WinAPI_CoTaskMemFree($pDispName)
    EndIf

    $oItemArr = 0
    $oItem = 0
    _WinAPI_FreeMemory($pItemArr)

    Return $aReturn
EndFunc   ;==>_DoFileOpen

Func _GetFileEncodingType($hFile)
    FileSetPos($hFile, 0, $FILE_BEGIN)
    
    ; Read the first few bytes to check for a Byte Order Mark (BOM)
    Local $sBOM = FileRead($hFile, 3)
    
    If BinaryMid($sBOM, 1, 3) == 0xEFBBBF Then 
        Return $FO_UTF8
    ElseIf BinaryMid($sBOM, 1, 2) == 0xFFFE Then 
        Return $FO_UTF16_LE
    ElseIf BinaryMid($sBOM, 1, 2) == 0xFEFF Then 
        Return $FO_UTF16_BE
    EndIf

    ; Reset position to start for regular UTF-8 / ANSI text parsing
    FileSetPos($hFile, 0, $FILE_BEGIN)
    
    Local $dChunk, $dByte, $iContBytes
    Local $iChunkSize = 4096, $iMaxChunks = 24 ; Check first 96k

    For $i = 1 To $iMaxChunks
        $dChunk = FileRead($hFile, $iChunkSize)
        If Not BinaryLen($dChunk) Then ExitLoop

        For $j = 1 To BinaryLen($dChunk)
            $dByte = BinaryMid($dChunk, $j, 1)

            If $iContBytes Then
                If BitAND($dByte, 0xC0) <> 0x80 Then Return $FO_ANSI
                $iContBytes -= 1
            ElseIf BitAND($dByte, 0x80) Then
                If BitAND($dByte, 0xE0) = 0xC0 Then
                    $iContBytes = 1
                ElseIf BitAND($dByte, 0xF0) = 0xE0 Then
                    $iContBytes = 2
                ElseIf BitAND($dByte, 0xF8) = 0xF0 Then
                    $iContBytes = 3
                Else
                    Return $FO_ANSI
                EndIf
            EndIf
        Next
    Next

    Return $FO_UTF8_NOBOM
EndFunc   ;==>_GetFileEncodingType

 

Posted

Just be careful in this section:
The double-equals does a string comparison.  We end up testing for "15711167" here because 0xEFBBBF becomes String(0xEFBBBF).

On 10/8/2026 at 8:45 AM, WildByDesign said:
If BinaryMid($sBOM, 1, 3) == 0xEFBBBF Then

What you had earlier works, otherwise just compare binary with binary.

If BinaryMid($sBOM, 1, 3) == "0xEFBBBF" Then 
If BinaryMid($sBOM, 1, 3) = Binary("0xEFBBBF") Then

I think the next step would be to build some BOM-Less UTF16 tests, rather than just returning ANSI if our UTF-8 test fails. It seems like there's a few tests that people use:

  • UTF16-LE:  Check for lots of nulls at odd bytes.  (Works for western languages, but bad test for Chinese/Japanese/Korean)
  • UTF16-BE:  Check for lots of nulls at even bytes.  (again, no good for CJK)
  • Look for line ending patterns/space characters. (No good if the text has none!)
  • CJK detection: Check if pairs of bytes fall consistently within certain ranges.

Also a bit off-topic: But this should be removed from the script too..
The garbage collector will free the object automatically once its refcount goes to 0.

$oItemArr = 0
 $oItem = 0
 _WinAPI_FreeMemory($pItemArr)

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...