1

I'm using the MRTK for a hololens app and I need to select a file that the user puts in their document folder. I am trying to access the FileOpenPicker and use the PickSingleFileAsync() function from a button press to get the file and then load that into my app.

The code inside this function is basically what I am doing:

(Code Source)

private async void PickAFileButton_Click(object sender, RoutedEventArgs e)
        {
            // Clear previous returned file name, if it exists, between iterations of this scenario
            OutputTextBlock.Text = "";

            FileOpenPicker openPicker = new FileOpenPicker();
            openPicker.ViewMode = PickerViewMode.Thumbnail;
            openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
            openPicker.FileTypeFilter.Add(".jpg");
            openPicker.FileTypeFilter.Add(".jpeg");
            openPicker.FileTypeFilter.Add(".png");
            StorageFile file = await openPicker.PickSingleFileAsync();
            if (file != null)
            {
                // Application now has read/write access to the picked file
                OutputTextBlock.Text = "Picked photo: " + file.Name;
            }
            else
            {
                OutputTextBlock.Text = "Operation cancelled.";
            }
        }

However, when I build and deploy to the Hololens Emulator, when I press the button It runs through the first part of the code just fine but I get an exception on this line

StorageFile file = await openPicker.PickSingleFileAsync();

After extensive research and some frustration I made a very poor and vague post about it here.

In that post I referenced this post which was made 2 years ago and says you can't do this but The Microsoft docs for Hololens say that you can use File pickers, in this case, FileOpenPicker.

I found this post buried in the Windows Mixed Reality Developer Forum that's related but isn't the issue I am having, I still felt it was necessary to include in this post.

I also want to add that I do have a file picker app installed. According to this post on Microsoft Docs if you call FileOpenPicker it will open whatever file picker was first installed on your device.

Also in the MRTK and the Appx that is being generated I am ensuring the permission to "PictureLibrary" capability is enabled.

Any help would be greatly appreciated, I feel I've waited too long to make a more formal post on this topic and I'm hoping to have some answers. Thanks!

1 Answers1

2

ALL THANKS TO THIS POST! I finally found a solution to my issue. The biggest call that wasn't mentioned in any docs I looked through was this UnityEngine.WSA.Application.InvokeOnUIThread()

I didn't run into this, nor did I find any documentation on this being a solution for MRTK+unity for hololens development. So for anyone out there looking for a solution to the issue I will quote that link above in case it's broken in the future.

#if !UNITY_EDITOR && UNITY_WSA_10_0
        Debug.Log("***********************************");
        Debug.Log("File Picker start.");
        Debug.Log("***********************************");

        UnityEngine.WSA.Application.InvokeOnUIThread(async () =>
        {
            var filepicker = new FileOpenPicker();
            // filepicker.FileTypeFilter.Add("*");
            filepicker.FileTypeFilter.Add(".txt");

            var file = await filepicker.PickSingleFileAsync();
            UnityEngine.WSA.Application.InvokeOnAppThread(() => 
            {
                Debug.Log("***********************************");
                string name = (file != null) ? file.Name : "No data";
                Debug.Log("Name: " + name);
                Debug.Log("***********************************");
                string path = (file != null) ? file.Path : "No data";
                Debug.Log("Path: " + path);
                Debug.Log("***********************************");

                

                //This section of code reads through the file (and is covered in the link)
                // but if you want to make your own parcing function you can 
                // ReadTextFile(path);
                //StartCoroutine(ReadTextFileCoroutine(path));

            }, false);
        }, false);

        
        Debug.Log("***********************************");
        Debug.Log("File Picker end.");
        Debug.Log("***********************************");
#endif

EDIT:

A little explanation as to what "InvokeOnUIThread" and "InvokeOnAppThread" mean.

Think of the way your MRTK app runs on the hololens, you have the app running in it's own little environment with a MRTK specific unity engine handling function calls in your app. That app is then running inside the bigger app (the OS) of the hololens. So whenever you make calls to UWP specific functions, things that only the hololens will understand, you NEED to call those functions inside the InvokeOnUIThread. Now while you're running inside that function call if you need to make any function calls back to functions that are MRTK, unity, or your own creation you need to use InvokeOnAppThread. This essentially tells the application that you want to make some UWP function calls then at any point if you need to pass back any information from the UWP function calls while still inside the InvokeOnUIThread call, you need to use InvokeOnAppThread.

From what I found out this is basically how this whole thing works and I figured this would be important to document.

  • I needed this too! I was able to get it to work in Unity 2020.3.9f1 with MRTK 2.7 preview 5. Many thanks for sharing your experience, greatly appreciated! – Larry Aultman May 22 '21 at 19:50
  • Awesome! I am glad you were able to utilize this! It was hard digging through post after post and not finding a solution without any clear explanation. I hope that this can help many others down the road. – Jonathan Reynolds May 23 '21 at 22:42
  • I also added an explanation on how each of the invoke functions work and what they mean. – Jonathan Reynolds May 23 '21 at 22:50
  • First of all huge thanks for this post! I'm still a little confused where to use await between thread jumps. I put all your code in an async method (StartPicker) and want to return the path (string) after the file dialog is finished. If I call StartPicker in another method and wait with await, it doesn't wait for completion which leaves the path still empty. I also tried to make a static method out of StartPicker and return a Task but I don't know how to get a Task from the Invoked Threads. – Alex Mar 09 '23 at 07:36
  • So because each thread runs independently you may want a sender and a receiver function. That way you can invoke on the UI thread (use await if you need to wait for an async call to finish before continuing on your current thread) and when you call invoke on app thread, use the receiver method in your function to push the string you found back to the place you want it. In your thread that's sitting and waiting you could just have a co-routine just waiting for a variable to become not null (ensure you have a timeout or it could loop for a long time and cause issues. – Jonathan Reynolds Mar 10 '23 at 18:36