Adding Preview Support In File Open Dialogue

SiamIT

Active member
Joined
Aug 3, 2021
Messages
40
Programming Experience
5-10
Greetings..

after some search on google, i found that i can use Microsoft.WindowsAPICodePack.Shell to add custom controls on file open/save dialogue box as needed.

so, i tried some, but i can't make it work to solve my purpose. I have added a button to preview music. But it throws a exception when it try to access FileName property. It looks like that property only accessible when click on "Open" button.

here is the code i tried:
Sample Code:
        private void sTest2()
        {
            //initial propeties
            CommonOpenFileDialog dlgOpen = new CommonOpenFileDialog()
            {
                AllowNonFileSystemItems = false,
                EnsureFileExists = true,
                EnsurePathExists = true,
                EnsureReadOnly = true,
                EnsureValidNames = true,
                InitialDirectory = musicPath,
                IsFolderPicker = false,
                Multiselect = false,
                Title = "Please Select The Music File You Like To Use As Background Music"
            };
            //filters
            dlgOpen.Filters.Add(new CommonFileDialogFilter("MP3 Music File", "*.mp3"));
            //custom controls
            CommonFileDialogButton button = new CommonFileDialogButton("Preview Music");
            dlgOpen.Controls.Add(button);
            button.Click += PreviewMusic;
            //custom event handler
            dlgOpen.FileOk += MusicFileSelected;

            CommonFileDialogResult dlgRet = dlgOpen.ShowDialog();
        }

        private void MusicFileSelected(object sender, CancelEventArgs e)
        {
            if (player.IsPlaying == true)
            {
                player.Stop();
            }
        }

        private void PreviewMusic(object sender, EventArgs e)
        {
            CommonFileDialogButton button = (CommonFileDialogButton)sender;
            CommonOpenFileDialog dlgOpen = (CommonOpenFileDialog)button.HostingDialog;

            if (string.IsNullOrWhiteSpace(dlgOpen.FileName) == true)
            {
                MessageBox.Show("Please select a music first!");
                return;
            }

            player.Play(dlgOpen.FileName);
        }

and here is the error i get:
C#:
File name not available - dialog has not closed yet.

is there any way, i can get the file name (with full path) when i click on the preview music button?

thanks in advance for any help..

best regards
 
I hope that there's a different implementation of the Windows API Code pack other than this one I stumbled across in GitHub. Basically you need access to the the IFileDialog at the item the OnSelectionChanged() event is fired so that you can call IFileDialog::GetCurrentSelection(). From what I'm seeing in that implementation, he is keeping his IFileDialog internal to his assembly. So you can't even derive from it and try to get access. :-(
 
I hope that there's a different implementation of the Windows API Code pack other than this one I stumbled across in GitHub. Basically you need access to the the IFileDialog at the item the OnSelectionChanged() event is fired so that you can call IFileDialog::GetCurrentSelection(). From what I'm seeing in that implementation, he is keeping his IFileDialog internal to his assembly. So you can't even derive from it and try to get access. :-(

so? there is nothing to do here? :( ?

is there any other library/code that can solve my case?

of course thanks for your response..
 
You could always fork the code and change that internal for the method GetNativeDialog() and make it at least protected if you plan on deriving another class from it, or public if you don't want to do any subclassing.

I don't have any other recommended libraries since I've not had any need for one yet and so have not run across any until your question came up.
 
You could always fork the code and change that internal for the method GetNativeDialog() and make it at least protected if you plan on deriving another class from it, or public if you don't want to do any subclassing.

I don't have any other recommended libraries since I've not had any need for one yet and so have not run across any until your question came up.

thanks again for your reply.. i tried ..to make the method "public"

but that leads to too many "Inconsistent accessibility:" error comes up, and when i make next one public, many others just comes up.. and it drive me nuts :(

so, i think, i am not the right person to fork that git :(

any way you can help me out?

best regards
 
Start from scratch and use C++/CLI to bring up the dialog, and use IJW to expose what is needed back to the C# world. That's assuming that your know C++ and how to use the Shell APIs .

Next alternative is to use P/Invoke to find the list view control, and then use more P/Invoke to send it Windows messages to query it what the current selected item is.
 
I think the easier approach here is to use Microsoft UI Automation - .NET Framework, here's an example that worked for me. Add at least these imports and references:
C#:
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Automation; //add reference UIAutomationClient and UIAutomationTypes
C#:
private void PreviewMusic(object sender, EventArgs e)
{
    CommonFileDialogButton button = (CommonFileDialogButton)sender;
    CommonOpenFileDialog dlgOpen = (CommonOpenFileDialog)button.HostingDialog;
    var title = dlgOpen.Title;

    Task.Run(() => // UI automation for own window must be on separate thread
    {
        var main = AutomationElement.FromHandle(Process.GetCurrentProcess().MainWindowHandle);
    
        var winCondition = new AndCondition(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window),
                                            new PropertyCondition(AutomationElement.NameProperty, title));
        var win = main.FindFirst(TreeScope.Children, winCondition);

        var list = win.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.List));
    
        var itemCondition = new AndCondition(new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem),
                                             new PropertyCondition(SelectionItemPattern.IsSelectedProperty, true));
        var item = list.FindFirst(TreeScope.Descendants, itemCondition); // could be child of list, or child of list group (descendant)
        if (item != null)
        {
            var barCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ToolBar);
            foreach (AutomationElement bar in win.FindAll(TreeScope.Descendants, barCondition))
            {
                var m = Regex.Match(bar.Current.Name, @"[A-Z]:\\.*");
                if (m.Success)
                {
                    var filepath = Path.Combine(m.Value, item.Current.Name);
                    Debug.WriteLine(filepath);
                }
            }
        }
    });
}
This application is useful for inspecting windows/elements for automation: Accessibility Insights for Windows · Accessibility Insights
 
The check on bar name should be changed to barname.IndexOf(":"), it's the only ToolBar that has a ":" in name (text like "Address: path"). The path could be for example localized "Music" or "Documents" instead of "x:\folder".
 
That preview will show for example for a text or image file, but not play a music file.
 
The Windows Shell allows custom file preview handlers to be installed.

 
Last edited:
If you don't want to go down that route, see the updated fork by contre. One of the pull requests actually lets the SelectionChangedEvent have access to the filenames and folders:


 
The check on bar name should be changed to barname.IndexOf(":"), it's the only ToolBar that has a ":" in name (text like "Address: path"). The path could be for example localized "Music" or "Documents" instead of "x:\folder".
A helper to find actual path from localized "known folder":
C#:
//using Microsoft.WindowsAPICodePack.Shell;
private static string PathFromLocalizedKnownFolder(string localname)
{
    foreach (var known in KnownFolders.All)
    {
        if (((ShellObject)known).IsFileSystemObject && known.LocalizedName == localname)
            return known.Path;
    }
    return null;          
}
 
Back
Top Bottom