Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IProjectTreeModifier doc should explain how to use an ICO file #46

Closed
AArnott opened this issue Jun 19, 2015 · 14 comments
Closed

IProjectTreeModifier doc should explain how to use an ICO file #46

AArnott opened this issue Jun 19, 2015 · 14 comments
Assignees

Comments

@AArnott
Copy link
Member

AArnott commented Jun 19, 2015

Now with the image monikers used by the image service in Dev14, we should lead customers to the docs that describe how to convert their ICO file to the image service to get an image moniker back.

This will be particularly relevant to the IProjectTreeModifier extension.

@AArnott
Copy link
Member Author

AArnott commented Jun 19, 2015

Note "Freddy D" asked this question on our blog.

@FreddyDgh
Copy link

Thanks for your response. It has already helped to make things a little clearer. As of right now, I've got a folder setup with four png files in it, with their Build Actions set to Resource. I'm essentially getting a SVsServiceProvider via MEF Import, creating a BitmapImage using the absolute pack uri, and then I'm playing around with using the following code:

    private static Microsoft.VisualStudio.Imaging.Interop.ImageMoniker getMoniker(string name, Microsoft.VisualStudio.Shell.SVsServiceProvider serviceProvider)
    {
        Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();

        System.Windows.Media.Imaging.BitmapImage resource = getResource(name);
        var img = Microsoft.Internal.VisualStudio.PlatformUI.WpfPropertyValue.CreateIconObject(resource);
        var imageService = serviceProvider.GetService(typeof(Microsoft.VisualStudio.Shell.Interop.SVsImageService)) as Microsoft.VisualStudio.Shell.Interop.IVsImageService2;
        var imgHandle = imageService.AddCustomImage(img);
        return imgHandle.Moniker;
    }

I'm not sure if this is the preferred method when using the new Project System Extensibility Preview or not, and it looks like I may still have threading issues to work out, but all I'm doing from there is setting the icon using thee IProjectTree.SetIcon method in my IProjectTreeModifier. If I'm missing something critical, it might be good if the documentation explained further. Thanks.

@AArnott
Copy link
Member Author

AArnott commented Jun 21, 2015

I like that in your code you're calling ThrowIfNotOnUIThread. Since tree modifier extensions are invoked from background threads you might compensate by making your method async and starting with a call to:

await ThreadHandling.AsyncPump.SwitchToMainThreadAsync();

@AArnott
Copy link
Member Author

AArnott commented Jun 21, 2015

On second thought, that may not work so well since IProjectTreeModifier is synchronous.

It's unfortunate that the image service adds thread affinity to the IProjectTreeModifier extensibility point at all, since that's supposed to be free-threaded. I've sent an email to the owners of the VS image service to ask if there is a free-threaded approach to this.

@FreddyDgh
Copy link

As far as anyone knows, I included ThrowIfNotOnUIThread in there purely based on my mastery of the threading model and not at all to placate a message that a code analyzer was giving me. But you seem to come to the same conclusion I did - it would help greatly if either IProjectTreeModifier was asynchronous or if there was a free-threaded way to use the image service. Otherwise, it looks like it becomes rather complicated fairly quickly just to do something that that feels like it should be a fundamental ability of the IProjectTreeModifier.

@Newrad0603
Copy link
Member

To avoid having to call the ImageService directly at all, you can create an image manifest that is deployed with your extension. Then create a moniker class to define your set of monikers. This lets you return the monikers directly, bypassing the need to query for the ImageService.

Sample manifest:

<?xml version="1.0" encoding="utf-8"?>
<ImageManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                           xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                           xmlns="http://schemas.microsoft.com/VisualStudio/ImageManifestSchema/2014">
  <Symbols>
    <String Name="Resources" Value="/<AssemblyName>;Component/" />
    <Guid Name="MyManifestGuid" Value="{bd27f6a0-f745-4588-94ec-5ebc994dd369}" />
    <ID Name="MyImage1" Value="0" />
    <ID Name="MyImage2" Value="1" />
  </Symbols>
  <Images>
    <Image Guid="$(MyManifestGuid)" ID="$(MyImage1)">
      <Source Uri="$(Resources)/<RelativePathTo>/MyImage1.png" />
    </Image>
    <Image Guid="$(MyManifestGuid)" ID="$(MyImage2)">
      <Source Uri="$(Resources)/<RelativePathTo>/MyImage2.png" />
    </Image>
  </Images>
  <ImageLists />
</ImageManifest>

Sample moniker code:

using System;
using Microsoft.VisualStudio.Imaging.Interop;

namespace My.Namespace
{
    public static class MyMonikers
    {
        private static readonly Guid MyManifestGuid = new Guid("{bd27f6a0-f745-4588-94ec-5ebc994dd369}");

        private const int MyImage1 = 0;
        private const int MyImage2 = 1;

        public static ImageMoniker MyImage1
        {
            get
            {
                return new ImageMoniker { Guid = MyManifestGuid, Id = MyImage1 };
            }
        }

        public static ImageMoniker MyImage2
        {
            get
            {
                return new ImageMoniker { Guid = MyManifestGuid, Id = MyImage2 };
            }
        }
    }
}

@FreddyDgh
Copy link

@Newrad0603 That's more of the approach I was expecting there would be, but the problem now is that I'm not finding any documentation on ImageManifest, either. Specifically, how does VS actually know about the ImageManifest? Is that file supposed to go in a specific place? Am I supposed to somehow reference the ImageManifest file in some other config/settings file in my extension? Or do I simply have to change the "Include in VSIX" property on the file to "True"?

@Newrad0603
Copy link
Member

Documentation for the ImageService is in the works, so sorry about that. VS searches for manifests the same way and in the same places it searches for extensions. Setting Include in VSIX to true will get your manifest to deploy so that the ImageService can find it.

@AArnott
Copy link
Member Author

AArnott commented Jun 27, 2015

@Newrad0603 Does that mean the manifest doesn't have to be explicitly identified by the vsixmanifest file?

@Newrad0603
Copy link
Member

@AArnott That is correct. The vsixmanifest does not contain any info for identifying an image manifest. The ImageService just looks for files with the .imagemanifest extension. So once your manifest is set to be included in the vsix, it will be picked up automatically after the extension is installed.

@FreddyDgh
Copy link

@AArnott @Newrad0603 Aaahhhhh ... the .imagemanifest extension is the critical piece that I was missing. I have got it working now. Thanks.

To summarize the entire process:
-Include Png files in your project and set the Build Action to Resource
-Add a file with a .imagemanifest extension and set Include in VSIX to True
-See sample code above for both creating the ImageManifest and using it in code
-Change the icons using the ApplyModifications method, as shown in the default item template for ProjectTreeModifier

The documentation should probably be updated either on Custom Icons Scenario Page or IProjectTreeModifier page.

The ImageManifest part is a little unintuitive, but gosh, the end result is still downright beautiful. Might help significantly if a new item template was added for an image manifest that would handle giving it the .imagemanifest extension, setting to inlcude in VSIX, and maybe just shoots out Newrad0603's sample file above. Could also perhaps inlcude an XML comment in there that says images should have their Build Action set to Resource.

@AArnott
Copy link
Member Author

AArnott commented Jun 29, 2015

Great. @adrianvmsft will be updating the docs soon.

@adrianvmsft
Copy link
Member

Thanks @Newrad0603 for the detailed instructions, I sent out a pull request with the documentation update #50

@AArnott AArnott closed this as completed Jul 12, 2015
@aodl
Copy link

aodl commented Jul 19, 2019

For other wanderers that stumble across this, note that that .imagemanifest should be located at the root of your project to be discoverable (include in VSIX == true, is not enough)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants