Skip to content
⌘ NSIS Forum Archive

Allowing user to multiselect different formats

8 posts

ggarra13#

Allowing user to multiselect different formats

I have a viewer and I would like to have file associations linked to my viewer. However, I would like to allow the user to select which files get associated. In summary, I would like to present a list of file extensions and act accordingly to the user selection.
I am an NSIS newbie, so please be gentle.
Anders#
This is a rather complicated topic. If you support all Windows versions and installing as non-admin then it becomes even more complicated.

For <= XP/2003 you can just force whatever you want (but they also support the default applications applet in control panel on XP SP1? and Windows 2000 SP4? and later), for Vista/7/8 there is a COM method you are supposed to call (defined at the bottom of \Include\Win\Com.nsh in NSIS) but this has been crippled on Windows 10 and just displays a message to the user.

The bottom line is that you cannot just present a list to the user anymore and expect to be able to set the defaults. I would recommend reading about file types and default applications on MSDN and maybe trying a couple of different designs on XP, 7/8 and 10 (if you support all 3 generations)...
ggarra13#
Currently, to register the file association I am using:

WriteRegStr HKCR '.bmp' '' 'mrViewer'

and similar for all other formats. Then I register mrViewer like:

WriteRegStr HKCR 'mrViewer' '' 'mrViewer'
WriteRegStr HKCR 'mrViewer\\\\shell' '' 'open'
WriteRegStr HKCR 'mrViewer\\\\shell\\\\open\\\\command' '' '$INSTDIR\\\\bin\\\\mrViewer.exe \\\"%1\\\"'

This seems to work for me and for all users. However, i am missing a GUI that would allow selecting the formats individually or, at the very least, a GUI front end which would have a single option to register ALL file types. That's what I need the help for.
Anders#
You have still not specified which Windows versions you intend to support so I'm going to just guess that you want Vista and later.

If you look at https://msdn.microsoft.com/en-us/lib...).aspx#install you see that the installer is supposed to register itself under RegisteredApplications and when a user runs your application for the first time the application is supposed to call IApplicationAssociationRegistrationUI::LaunchAdvancedAssociationUI.

You can do it in NSIS but that is not what the MS best practices are telling you to do.

!define MyRegisteredApplicationName "Windows Photo Viewer" ; Using Windows Photo Viewer as a example here
!include LogicLib.nsh
!ifndef CLSID_ApplicationAssociationRegistrationUI
!define CLSID_ApplicationAssociationRegistrationUI {1968106d-f3b5-44cf-890e-116fcb9ecef1}
!endif
!ifndef IID_IApplicationAssociationRegistrationUI
!define IID_IApplicationAssociationRegistrationUI {1f76a169-f994-40ac-8fc8-0959e8874710} 
!endif
System::Call 'OLE32::CoCreateInstance(g "${CLSID_ApplicationAssociationRegistrationUI}",i 0,i 0x1,g "${IID_IApplicationAssociationRegistrationUI}",*p.r1)i.r0'
${If} $0 = 0
    System::Call `$1->3(w "${MyRegisteredApplicationName}")i.r0`
    System::Call `$1->2()`
${EndIf} 
If you want to present a list to the user during installation so they can choose which extensions you write to HKCR you can create a custom page with a ListBox or a ListView on it:

!include nsDialogs.nsh
Var hListCtl
Page Custom LBPageCreate LBPageLeave
Page InstFiles
Function LBPageCreate
nsDialogs::Create 1018
Pop $0
nsDialogs::CreateControl ${__NSD_ListBox_CLASS} ${__NSD_ListBox_STYLE}|${LBS_MULTIPLESEL} ${__NSD_ListBox_EXSTYLE} 0 0 100% 100% ""
Pop $hListCtl
${NSD_LB_AddString} $hListCtl ".bmp"
${NSD_LB_AddString} $hListCtl ".jpg"
${NSD_LB_AddString} $hListCtl ".png"
SendMessage $hListCtl ${LB_SELITEMRANGE} 1 0xffff0000 ; Select all
nsDialogs::Show
FunctionEnd
Function LBPageLeave
${NSD_LB_GetCount} $hListCtl $0
${For} $1 0 $0
    SendMessage $hListCtl ${LB_GETSEL} $1 "" $2
    ${IfThen} $2 <= 0 ${|} ${Continue} ${|}
    System::Call 'USER32::SendMessage(p$hListCtl,i ${LB_GETTEXT}, i $1, t.r2)'
    MessageBox mb_ok "TODO: Register $2"
${Next}
FunctionEnd 
!include nsDialogs.nsh
!include WinMessages.nsh
!ifndef LVM_GETITEMTEXT
!define /math LVM_GETITEMTEXTA ${LVM_FIRST} + 45
!define /math LVM_GETITEMTEXTW ${LVM_FIRST} + 115
${_NSIS_DEFAW} LVM_GETITEMTEXT
!endif
Var hListCtl
Page Custom LVPageCreate LVPageLeave
Page InstFiles
Function AddCheckedListViewItemWith1SubItem
System::Store S
Pop $3
Pop $2
Pop $1
System::Call '*(i ${LVIF_TEXT},i 0x7fffffff,i 0,i,&i${NSIS_PTR_SIZE},tr2,i,i,p)p.r9'
SendMessage $1 ${LVM_INSERTITEM} "" $9 $0
System::Call '*$9(i${LVIF_STATE},i,i,i0x2000,&i${NSIS_PTR_SIZE} ${LVIS_STATEIMAGEMASK},p,i,i,p)'
SendMessage $1 ${LVM_SETITEMSTATE} $0 $9 $8
System::Call '*$9(i,i 0x7fffffff,i 1,i,i,tr3,i,i,p)'
SendMessage $1 ${LVM_SETITEMTEXT} $0 $9
System::Free $9
System::Store L
FunctionEnd
!macro AddCheckedListViewItemWith1SubItem hLV txt sub1
Push ${hLV}
Push "${txt}"
Push "${sub1}"
Call AddCheckedListViewItemWith1SubItem
!macroend
Function LVPageCreate
nsDialogs::Create 1018
Pop $0
nsDialogs::CreateControl "SysListView32" ${DEFAULT_STYLES}|${WS_TABSTOP}|${WS_VSCROLL}|${LVS_REPORT}|${LVS_SORTASCENDING} ${WS_EX_WINDOWEDGE}|${WS_EX_CLIENTEDGE} 0 0 100% 100% ""
Pop $hListCtl
IntOp $0 ${LVS_EX_FULLROWSELECT} | ${LVS_EX_CHECKBOXES}
SendMessage $hListCtl ${LVM_SETEXTENDEDLISTVIEWSTYLE} 0 $0
System::Call '*(i${LVCF_TEXT}|${LVCF_SUBITEM},i,i,t "Extension",i,i 0)p.r9'
SendMessage $hListCtl ${LVM_INSERTCOLUMN} 0x7fffffff $9
System::Call '*$9(i,i,i,t "Description",i,i 1)'
SendMessage $hListCtl ${LVM_INSERTCOLUMN} 0x7fffffff $9
System::Free $9
!insertmacro AddCheckedListViewItemWith1SubItem $hListCtl ".bmp" "Windows/OS2 Bitmap"
!insertmacro AddCheckedListViewItemWith1SubItem $hListCtl ".png" "Portable Network Graphics"
SendMessage $hListCtl ${LVM_SETCOLUMNWIDTH} 0 -1
SendMessage $hListCtl ${LVM_SETCOLUMNWIDTH} 1 -1
nsDialogs::Show
FunctionEnd
Function LVPageLeave
System::Call '*(&t${NSIS_MAX_STRLEN},i)p.r8'
System::Call '*(i ${LVIF_TEXT},i,i 0,i,&i${NSIS_PTR_SIZE},pr8,i${NSIS_MAX_STRLEN},i,p)p.r9'
SendMessage $hListCtl ${LVM_GETITEMCOUNT} "" "" $1
StrCpy $0 0
${DoWhile} $0 < $1
    SendMessage $hListCtl ${LVM_GETITEMSTATE} $0 ${LVIS_STATEIMAGEMASK} $2
    IntOp $2 $2 & 0x2000
    ${If} $2 <> 0
        SendMessage $hListCtl ${LVM_GETITEMTEXT} $0 $9 $2
        System::Call '*$8(&t${NSIS_MAX_STRLEN}.r7)'
        MessageBox mb_ok "TODO: Register $7"
    ${EndIf}
    IntOp $0 $0 + 1
${Loop}
System::Free $8
System::Free $9
FunctionEnd 
(These examples show the chosen extensions in the page leave callback function but you really should store them in a .ini in $pluginsdir and then actually write to the registry in a Section)
ggarra13#
Anders, thanks for the examples. The third example, with the button, extension and description is what I was looking for. Any chance of modifying the AddCheckedListViewItemWith1SubItem macro so it takes a final parameter of 1 or 0 to set the button on as a default?
Also, I followed your advice and save all selected extensions into an .ini file, so that I can later uninstall them. The saving is working well, but I may need some help with the uninstall part, too.
I make little progress as I am using cmake's NSIS extension which is not the most helpful.
Anders#
Originally Posted by ggarra13 View Post
Anders, thanks for the examples. The third example, with the button, extension and description is what I was looking for. Any chance of modifying the AddCheckedListViewItemWith1SubItem macro so it takes a final parameter of 1 or 0 to set the button on as a default?
Function AddCheckedListViewItemWith1SubItem 
System::Store S 
Pop $4
Pop $3 
Pop $2 
Pop $1 
System::Call '*(i ${LVIF_TEXT},i 0x7fffffff,i 0,i,&i${NSIS_PTR_SIZE},tr2,i,i,p)p.r9' 
SendMessage $1 ${LVM_INSERTITEM} "" $9 $0 
System::Call '*$9(i${LVIF_STATE},i,i,i0x2000,&i${NSIS_PTR_SIZE} ${LVIS_STATEIMAGEMASK},p,i,i,p)' 
IntCmpU $4 0 +2
SendMessage $1 ${LVM_SETITEMSTATE} $0 $9 $8 
System::Call '*$9(i,i 0x7fffffff,i 1,i,i,tr3,i,i,p)' 
SendMessage $1 ${LVM_SETITEMTEXT} $0 $9 
System::Free $9 
System::Store L 
FunctionEnd 
!macro AddCheckedListViewItemWith1SubItem hLV txt sub1 checked
Push ${hLV} 
Push "${txt}" 
Push "${sub1}" 
Push "${checked}"
Call AddCheckedListViewItemWith1SubItem 
!macroend 
...
!insertmacro AddCheckedListViewItemWith1SubItem $hListCtl ".bmp" "Windows/OS2 Bitmap" 0 
ggarra13#
Thank you very much, Anders. I was able to create my installer/unistaller with your help and with an .ini file.

However, one thing I noticed was that when I was in the page to select extensions, the exit button of the window, would not work. Also, the scroller would not respond to the mousewheel. Any ideas why this should be so? (One note: I am using the NSIS code thru cmake/cpack, albeit I don't think it has much to do about this).
Anders#
Originally Posted by ggarra13 View Post
Thank you very much, Anders. I was able to create my installer/unistaller with your help and with an .ini file.

However, one thing I noticed was that when I was in the page to select extensions, the exit button of the window, would not work. Also, the scroller would not respond to the mousewheel. Any ideas why this should be so? (One note: I am using the NSIS code thru cmake/cpack, albeit I don't think it has much to do about this).
Scrolling works if you click the control first. It does not get focus because nsDialogs probably figures that the next button should have focus? Fix it by adding
System::Call 'USER32::PostMessage(p $hwndparent, i ${WM_NEXTDLGCTL}, p $hListCtl, i1)' 
before nsDialogs::Show.

I don't know what the exit button is. The close button might be disabled on pages after the InstFiles page if that is what you mean...