2

What I want?

I want to call up the Open with dialog that is only used to specify the format file association and will not open the file.

Look like this in Win10:

Win10 Open With Dialog

And look like this in Win11:

Win11 Open With Dialog

In system settings, you can modify the default application to display the open with dialog.

Existing third-party software implementation: Bandizip can do it!

What I tried?

Call the obsoleted Win32 API

MSDN doc says that starting in Windows 10, it is not possible to call SHOpenWithDialog to set the file association for the specified format.

Call the system command line

When I tried to invoke the following two commands, neither of the "Open With" dialog boxes were able to set the file association, and will open the file.

  • OpenWith.exe "%1"
  • rundll32.exe shell32.dll,OpenAs_RunDLL "%1"

%1 is a full file path.

Use openas Verb

I tried to start a process with openas verb whether with calling CreateProcess or ShellExecuteEx, I think they do the same thing in the end. The "Open With" dialog will displayed with a check box named "Always open with this application" as-if clicking "Open With" from a file's context menu. But that will open the file, and an exception is thrown for an extension with no file association set.

  • CreateProcess
public static void ShowOpenWithDialog(string extension)
{
    string tempPath = $"{Path.GetTempPath()}{Guid.NewGuid()}{extension}";
    File.WriteAllText(tempPath, "");
    using(Process process = new Process())
    {
        process.StartInfo = new ProcessStartInfo
        {
            UseShellExecute = true,
            FileName = tempPath,
            Verb = "openas"
        };
        process.Start();
    }
    File.Delete(tempPath);
}
struct ShellExecuteInfo
{
    public int Size;
    public uint Mask;
    public IntPtr hwnd;
    public string Verb;
    public string File;
    public string Parameters;
    public string Directory;
    public uint Show;
    public IntPtr InstApp;
    public IntPtr IDList;
    public string Class;
    public IntPtr hkeyClass;
    public uint HotKey;
    public IntPtr Icon;
    public IntPtr Monitor;
}

[DllImport("shell32.dll")]
static extern bool ShellExecuteEx(ref ShellExecuteInfo lpExecInfo);

const uint SW_NORMAL = 1;

static void ShowOpenWithDialog()
{
    string tempPath = $"{Path.GetTempPath()}{Guid.NewGuid()}{extension}";
    File.WriteAllText(tempPath, "");
    var sei = new ShellExecuteInfo
    {
        sei.Size = Marshal.SizeOf(sei),
        sei.Verb = "openas",
        sei.File = tempPath,
        sei.Show = SW_NORMAL,
    };
    ShellExecuteEx(ref sei);
}

Call Launcher.LaunchFileAsync in UWP

Launch the default app for a file - Open With launch

I found that I can call up the open with dialog through this method, but the effect is no different from the above method.

async void DefaultLaunch()
{
   // Path to the file in the app package to launch
      string imageFile = @"images\test.png";
   var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(imageFile);
   if (file != null)
   {
      // Set the option to show the picker
      var options = new Windows.System.LauncherOptions();
      options.DisplayApplicationPicker = true;
      // Launch the retrieved file
      bool success = await Windows.System.Launcher.LaunchFileAsync(file, options);
      if (success)
      {
         // File launched
      }
      else
      {
         // File launch failed
      }
   }
   else
   {
      // Could not find file
   }
}
  • I'm sorry, I chose C# in the tag, and it automatically changed to C. It seems that # is a special letter, this is the first time I asked here, please forgive me. – 蓝点lilac Aug 17 '22 at 11:51
  • According to the Doc:[File Association Example](https://learn.microsoft.com/en-us/windows/win32/shell/fa-sample-scenarios) default file associations work changed in Windows 10. With Windows 10, all apps – both Classic Windows apps and Universal Windows apps – will be unable to invoke a prompt to change your defaults, only Windows. – Jeaninez - MSFT Aug 18 '22 at 05:33
  • 1
    This is by design Microsoft doesn't want anyone to mess with a user's file association. You can do it with UI automation (emulating an end-user actions) but it an be complicated see here: https://stackoverflow.com/a/52198802/403671 – Simon Mourier Aug 18 '22 at 08:35
  • Thanks Simon Mourier, but UI automation doesn't work very well, it's complicated, and I'm not going to consider it for now. – 蓝点lilac Aug 19 '22 at 01:48
  • https://stackoverflow.com/questions/4726441/how-can-i-show-the-open-with-file-dialog – Hans Passant Mar 06 '23 at 01:18

1 Answers1

0

My colleague accidentally found that could click button for change open with in the file properties dialog to call up the open with dialog, so I thought of the method of simulating clicking.

My friend find that Bandizip is also implemented in this way by intercepting its message.

It was verified after the test on the slow-motion virtual machine. The file properties dialog flashed, and then the open with dialog popped up.

The following is my implementation code for using C# to simulate clicking.

[DllImport("shell32.dll")]
static extern bool SHObjectProperties(IntPtr hWnd, int shopObjectType,
    [MarshalAs(UnmanagedType.LPWStr)] string pszObjectName,
    [MarshalAs(UnmanagedType.LPWStr)] string pszPropertyPage);

[DllImport("user32.dll")]
static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

[DllImport("kernel32.dll")]
static extern uint GetCurrentProcessId();

[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

[DllImport("User32.dll")]
static extern int SetForegroundWindow(IntPtr point);

[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int cmdShow);

[DllImport("user32.dll")]
static extern bool IsWindowVisible(IntPtr hWnd);

[DllImport("user32.dll")]
static extern bool IsWindowEnabled(IntPtr hWnd);

[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

[DllImport("user32.dll")]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);

[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);

[DllImport("user32.dll")]
static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);

const string CLASSNANE_DIALOG = "#32770";
const int SHOP_FILEPATH = 0x2;
const int MAX_PATH = 260;
const int GWL_EXSTYLE = -20;
const int WS_EX_LAYERED = 0x80000;
const int LWA_ALPHA = 0x2;
const int ID_APPLY = 0x3021;
const int WM_CLOSE = 0x010;
const int VK_LMENU = 0xA4;
const int VK_C = 0x43;
const int KEYEVENTF_KEYUP = 0x2;

public static void ShowOpenWithDialog(string extension)
{
    string fileName = Guid.NewGuid().ToString() + extension;
    string filePath = Path.Combine(Path.GetTempPath(), fileName);
    File.WriteAllText(filePath, string.Empty);
    bool found = false;
    uint currentId = GetCurrentProcessId();
    bool func(IntPtr hWnd, IntPtr lparam)
    {
        GetWindowThreadProcessId(hWnd, out uint id);
        if(id == currentId)
        {
            var sb = new StringBuilder(MAX_PATH);
            if(GetClassName(hWnd, sb, sb.Capacity) != 0 && sb.ToString() == CLASSNANE_DIALOG)
            {
                if(GetWindowText(hWnd, sb, sb.Capacity) != 0 && sb.ToString().Contains(fileName))
                {
                    found = true;
                    do { ShowWindow(hWnd, 0); }
                    while(IsWindowVisible(hWnd));
                    SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) ^ WS_EX_LAYERED);
                    SetLayeredWindowAttributes(hWnd, 0, 0, LWA_ALPHA);
                    Task.Run(() =>
                    {
                        IntPtr applyHandle = GetDlgItem(hWnd, ID_APPLY);
                        while(!IsWindowEnabled(applyHandle)) Thread.Sleep(100);
                        SendMessage(hWnd, WM_CLOSE, 0, 0);
                    });
                    SetForegroundWindow(hWnd);
                    keybd_event(VK_LMENU, 0, 0, 0);
                    keybd_event(VK_C, 0, 0, 0);
                    keybd_event(VK_LMENU, 0, KEYEVENTF_KEYUP, 0);
                    keybd_event(VK_C, 0, KEYEVENTF_KEYUP, 0);
                    File.Delete(filePath);
                }
            }
        }
        return true;
    }
    Task.Run(() =>
    {
        while(!found)
        {
            EnumWindows(func, IntPtr.Zero);
            Thread.Sleep(50);
        }
    });
    SHObjectProperties(IntPtr.Zero, SHOP_FILEPATH, filePath, null);
}

Note that none of the three ways I know of to open the file properties dialog will get a window handle to the dialog. Bandizip uses ShellExecuteEx, and I use SHObjectProperties. Nor you can hide a window when calling ShellExecuteEx by setting the nShow property of the SHELLEXECUTEINFO parameter to SW_HIDE.

So you need to enumerate the window after opening the file properties dialog, find it by process id, window class name, and title, and hide it and set it's transparency to 0.Because the Windows Message Queuing mechanism does not always process messages quickly, it may be necessary to send hidden window messages several times to quickly hide the file properties dialog box and reduce its flicker probability.

Then send the ALT + C shortcut key to the Properties dialog to simulate clicking the Change button, wait a moment, and the open with dialog I want will be displayed.

The Apply button in the file properties dialog becomes enabled whether you click OK in the open with dialog or you click outside the scope of the dialog to lose focus and close it, so you can check to see if the open with dialog is closed by iterating through the enabled status of the Apply button and then closing the File Properties dialog. And do something you want to do.