Issue with shared WinForms resources across projects in Visual Studio
Asked Answered
S

7

16

I've moved all image resources in a large multi-project solution to a new project specifically created for shared resources.

Adding a new reference to other projects where the shared images are used is working as expected. The image in the selected button below continues to appear in designer but with issues.

In VS Designer, I'm unable to select shared resources in the Image Editor, having instead to manually edit the designer code.

More specifically, in the Image Editor dialog (accessed from the 'Image' property of an existing control) I select 'Select from File/Resource..." to pop up the Select Resource dialog (shown). There, where I used to access all local project resources (Resources.resx), I would like now to add access to the new shared resources project. Ideally, I would add a second item to the dropdown shown which switches to the resx for the shared resources project.

How can this be done? Is there a better way?

alt text

Squint answered 8/7, 2009 at 20:43 Comment(2)
Winforms, web forms, version?Loni
WinForms -- .NET 3.5 -- VS 2008Squint
B
25

I was able to share a resx file between project and using it in the designer in VS 2008 SP1 (I don't know other versions). Differently from other approaches, here resources are statically and dynamically shared, so at runtime there's really one copy of the shared resources. No storage or memory waste and it's easy to maintain with tons of projects.

Follow the guide and tell me if it works for you:

  • Create a class project Resources;
  • Create a resource file (go to Properties -> Resources of the project);
  • Move the resource file (default Resources.resx) to the root of the project;
  • Rename the resources file Resources.resx -> GlobalResources.resx;
  • Modify the resources file modifier to Public (double click on the resources file, "Access Modifier" -> "Public");
  • Copy any resource file to the root directory of the project;
  • Add any resource to the resource file using "Add Existing File" and selecting resources on the root directory of the project.

The project in the Solution Explorer should look like this:

enter image description here

Now in any project you need the resx file to be shared do this:

  • Add the "Resource" project as a dipendency;

  • Edit manually the project file (*.csproj) and add the following lines in the resources file ItemGroup (create a local resource file if you want to see it):

     <ItemGroup>
       ...
       <EmbeddedResource Include="..\Resources\GlobalResources.resx">
         <Link>GlobalResources.resx</Link>
         <Generator>ResXFileCodeGenerator</Generator>
         <LastGenOutput>GlobalResources.Designer.cs</LastGenOutput>
         <CustomToolNamespace>Resources</CustomToolNamespace>
       </EmbeddedResource>
       ...
     </ItemGroup>
    

Obviously, the "Include" attribute should be the correct relative path to GlobalResources.resx.

  • Use the resources in the designer as you was asking.

In my project, the following lines are generated and added to the project automatically when I do this:

<Compile Include="..\Resources\GlobalResources.Designer.cs">
  <Link>GlobalResources.Designer.cs</Link>
  <AutoGen>True</AutoGen>
  <DesignTime>True</DesignTime>
  <DependentUpon>GlobalResources.resx</DependentUpon>
</Compile>

If they aren't added, add them manually.

Now the project should look like this in the Solution Explorer (likend files should be marked differently):

enter image description here

Last two steps:

  • Click on the linked "GlobalResources.resx" and on the Properties set "None" as the "BuildAction". "Custom Tool Namespace" should already be set to "Resources";

enter image description here

  • Click on the linked "GlobalResources.Designer.resx" and on the Properties set "None" as the "BuildAction".

enter image description here

And you are done: at this point you should really be able to use the resources you added in the shared resx in the designer selecting "GlobalResources.resx" in the "Project Resource file" dialog of your question. And as told it's really shared even at runtime! In the Properties panel you should see the full Namespace and class of the external project. If you remove the dependency on the "Resource" project it doesn't even compile. If it doesn't work for you, insist until it works and in case tell me where the guide is wrong. It have to work! Let me know.

Blood answered 7/6, 2011 at 19:24 Comment(8)
This answer seems to be near perfect, the only issue I have is the CustomToolNamespace is still relative to the Project Default Namespace, don't suppose you know a way for this to "override" the default namespace?Catfall
I don't use that feature, sorry. Unsure of what is useful for, can you make an example? After all, do you really need to bother with namespaces when you have a shared project with all the resources dynamically available for the whole solution?Blood
Thanks...this is a better solution than the one I posted. The trick I missed is to set the "BuildAction" to "None".Lacagnia
What does setting the BuildAction to None do?Winsor
It makes GlobalResources.resx to not compile on each project that uses it, keeping a single instance in one shared assembly.Blood
Not sure if others get the same issue as me on VS2013. I followed the above to the letter (I am sure!). All compiles. But when I add a new image to the GlobalResources.resx in the resource class lib project (ie my own MyProject.MyApp.UI), when I compile, I get errors on all lines in the project where I refer to the images - ie MyProject.MyApp.UI.GlobalResources.Image1 - typically in my helper classes. Any thoughts on this?Sharecropper
Looks like the designer plays funny b^&^$%. It changes the namespace and public modifier when I add an image through the designer in the referenced project. So in the MyProject.MyApp.App (exe project with forms in) I add an image to the picture box in the designer and the GlobalResources resource namespace in the MyProject.MyApp.UI changes to MyProject.MyApp.App. Marvelous!Sharecropper
@Sharecropper you have to change the Generator in the referencing project to something invalid like "none". See my AnswerFlax
L
9

I came accross the same problem recently. Looked around MS documentation but couldn't find any good solutions. I was eventually able to get the image editor in the WinForms designer to recognize resource files from other projects using the method described below. It is hacky - if anyone has a cleaner solution they want to share please post. I have tested this on VS 2008 only.

Before I start I should say that if you just want to make the items in a Resources.resx file accessible to other projects, you can just edit its properties and set the access modifier for the generated class to public so it uses PublicResXFileCodeGenerator. The gymnastics below are only needed if you want to get the WinForms designers to pick up images inside this resource file as described in the post above from Douglas.

Say that your solution has the following layout. The default namespace for the various projects (right click on a project node in solution explorer -> Properties -> Application tab) is important here, so I've put this in brackets after the project names.

  • MySolution
    • SharedProject (default namespace = SharedProject)
      • SharedProject.csproj
    • WinFormsProject1 (default namespace = WinFormsProject1)
      • WinFormsProject1.csproj
    • WinFormsProject2 (default namespace = WinFormsProject2)
      • WinFormsProject2.csproj

You want to create a resource file in SharedProject containing images that can be used by WinFormsProject1 and WinFormsProject2.

First create a new Resource file in the root of SharedProject, let's call it SharedResources.resx. It is important that this file uses ResXFileCodeGenerator rather than PublicResXFileCodeGenerator for reasons which will become clear later.

Edit SharedResources.resx in Visual Studio and add the image resources you want to share. Visual Studio should also generate a class file for you inside SharedProject called SharedResources.designer.cs.

Your solution should now look like this:

  • MySolution
    • SharedProject (default namespace = SharedProject)
      • SharedProject.csproj
      • SharedResources.resx
      • SharedResources.Designer.cs
    • WinFormsProject1 (default namespace = WinFormsProject1)
      • WinFormsProject1.csproj
    • WinFormsProject2 (default namespace = WinFormsProject2)
      • WinFormsProject2.csproj

If you open up SharedProject.csproj with a text editor you should see the following entries:

<EmbeddedResource Include="SharedResources.resx">
      <Generator>ResXFileCodeGenerator</Generator>
      <LastGenOutput>SharedResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<Compile Include="SharedResources.Designer.cs">
      <AutoGen>True</AutoGen>
      <DesignTime>True</DesignTime>
      <DependentUpon>SharedResources.resx</DependentUpon>
</Compile>

Now you need to make the resources accessible inside WinFormsProject1. To do this you'll need edit WinFormsProject1.csproj in a text editor because some of the properties you need to set are not exposed inside Visual Studio.

Open up the WinFormsProject1.csproj file and add the following inside an ItemGroup:

<Compile Include="..\SharedProject\SharedResources.Designer.cs">
    <Link>SharedResources.Designer.cs</Link>
    <AutoGen>True</AutoGen>
    <DesignTime>True</DesignTime>
    <DependentUpon>SharedResources.resx</DependentUpon>
</Compile>
<EmbeddedResource Include="..\SharedProject\SharedResources.resx">
    <Link>SharedResources.resx</Link>
    <Generator>ResXFileCodeGenerator</Generator>
    <LastGenOutput>SharedResources.Designer.cs</LastGenOutput>
    <CustomToolNamespace>SharedProject</CustomToolNamespace>
    <LogicalName>SharedProject.SharedResources.resources</LogicalName>
</EmbeddedResource>

The key things here are CustomToolNamespace and LogicalName. By default Visual Studio uses the project's default namespace to name resources and to generate the *.Designer.cs file. Since SharedProject and WinFormsProject1 have different default namespaces the default behaviour causes the resource that is embedded in WinFormsProject1 to be incompatible with the *.Designer.cs file that is inside SharedProject. This can be prevented by overriding CustomToolNamespace and LogicalName.

Now do the same for WinFormsProject2.csproj. Go back to Visual Studio. It will notice that you've changed the csproj files and ask to reload the projects - choose "reload". You should find that you can now choose images from SharedResources.resx when designing forms in both WinFormsProject1 and WinFormsProject2.

Essentially what all of this does is make VS include the SharedResources.resx file from SharedProject in the WinFormsProject1 and WinFormsProject2 projects. There is only one .resx source file (in SharedProject), but when you compile the solution there will actually be three identical classes called SharedProject.SharedResources, one in each of the three assemblies, but since all have internal visibility they won't interfere with one another even if you add a reference between the projects.

Lacagnia answered 30/1, 2011 at 22:29 Comment(3)
I don't know how you were able to figure this out but it worked perfectly for me in VS 2010!!!!!!Tharpe
This solution has a caveat, you must remember that you aren't depending on the "SharedResource" library, but infact compiling those resources directly into your application.Catfall
Using this approach if you had an application and 2 dependent libraries which needed access to the SharedResources then you would end up with 3 copies of the resources. So I think the best way to look at this answer is if you wish to use SharedResources as a "template" library of resources to be "included" in your app, versus having a SharedResource from which your projects can depend on.Catfall
B
7

SHARING RESOURCES BETWEEN PROJECTS IN VS2012

in 3 easy steps using the designer only!

  1. create a project and add your resources to be shared as usual
  2. create another project and 'add as link' the resources.resx of the first project in the 'add existing item' Dialog, accessible from the context menu
  3. select images from the linked resource file as desired; this can be selected in the 'project resource file' pull down menu within the 'select resource' Dialog, accessible from the properties pane

VOILA!

Brottman answered 6/5, 2013 at 6:19 Comment(3)
That kind of works, but try adding images to the GlobalResources in the resource project once you have referenced images from code in the other projects (that reference the resource class lib project). Then zut alores!Sharecropper
Seems to be when I add use the designer to add an image to a picture box in the referenced projects.Sharecropper
This was the only solution that worked, but I needed to change the Access Modifier to public. This is located in the Resource Design editor, to the right of "Remove Resource"Appear
M
2

For Visual Studio 2015+

There is an easy, straightforward way, that is not supported in the VisualStudio UI. So if you are not afraid of editing the project files, read on.

If you have not already done so, add a new shared project into the solution.Add a New Shared Projecct

If you have not already done so, include the new shared project as a shared project reference to your peripheral project.enter image description here

Note how this shared project is included in the peripheral project (.csproj file) in a straight forward manner:

<Import Project="..\Common\Common.projitems" Label="Shared" />

Thus, even though the VS UI does not expose it, everything in the shared project (.projitems file) is included directly into the peripheral project. Adding the following resource definitions to the shared project (.projitems file) will include them directly into your peripheral project. But, because the Resource element is not supported by the VS UI for shared projects, these files will be hidden and ignored in the IDE.

<ItemGroup>
  <Resource Include="$(MSBuildThisFileDirectory)Resources\one 16x16.png" />
  <Resource Include="$(MSBuildThisFileDirectory)Resources\one 64x64.png" />
  <Resource Include="$(MSBuildThisFileDirectory)Resources\one.ico" />
  <Resource Include="$(MSBuildThisFileDirectory)Resources\two 16x16.png" />
  <Resource Include="$(MSBuildThisFileDirectory)Resources\three 16x16.png" />
  <Resource Include="$(MSBuildThisFileDirectory)Resources\four 16x16.png" />
  <Resource Include="$(MSBuildThisFileDirectory)Resources\five 16x16.png" />
  <Resource Include="$(MSBuildThisFileDirectory)Resources\six 16x16.png" />
</ItemGroup>

Lastly, whereas your peripheral project resources might have relative paths (e.g. ".\Resources\main.ico"), these resources are compiled into your peripheral project with base ids, not absolute or relative. e.g.:

<Window x:Class="Common.Shared"
  ...
  Icon="one.ico">
  <Window.Resources>
    <ResourceDictionary>
      <BitmapImage x:Key="one64" UriSource="one 64x64.png"/>
    </ResourceDictionary>
  </Window.Resources>
Mountie answered 2/8, 2017 at 21:27 Comment(1)
This partially helps me but how do you reference a string resource within a file contained in the shared project?Tye
F
1

You only need add a new class project with a RESX file where you include the resources (images, texts, etc). After in the other projects add a reference to this and use the Resource class name in order to access the resource needed, for example: Project.SharedImages.Upload_64x64 (the result is a Bitmap ready to use in this case)

Fungal answered 16/9, 2012 at 0:50 Comment(1)
The Resource will not be available in Designer when for example setting an Icon of a controlFlax
E
0

Charlie's answer works great!

Just a small change: If you want to skip the last two steps, use this version of the included items when you modify a project file.

<None Include="..\Resources\GlobalResources.resx">
      <Link>GlobalResources.resx</Link>
      <Generator>PublicResXFileCodeGenerator</Generator>
      <LastGenOutput>GlobalResources.Designer.cs</LastGenOutput>
      <CustomToolNamespace>Resources</CustomToolNamespace>
</None>
<None Include="..\Resources\GlobalResources.Designer.cs">
      <Link>GlobalResources.Designer.cs</Link>
      <DependentUpon>GlobalResources.resx</DependentUpon>
</None>
Ergonomics answered 1/1, 2021 at 20:48 Comment(1)
Generator must be set to something invalid like "none". See my answer.Flax
F
0

Use the following approach for SDK Style projects or copy the contents of the .targets file (not tested) for non-SDK Style projects.

1: Create the following file next to your solution. Replace the placeholders with the proper information of your global resource. Name it for example "IncludeGlobalResources.targets"

<Project>
  <!--Makes it possible to select the Images of My.Custom.Namespace.MyGlobalResourceProject in your Projects WindowsForms-Designers-->
  <!--See https://mcmap.net/q/538709/-issue-with-shared-winforms-resources-across-projects-in-visual-studio>
  <ItemGroup>
    <None Include="..\My.Namespace.MyGlobalResourceProject\MyGlobalImages.resx">
      <Link>MyGlobalImages.resx</Link>
      <LastGenOutput>MyGlobalImages.Designer.cs</LastGenOutput>
      <Generator>none</Generator> <!-- Without this line you will get bugs as the referencing project will re-generate the source file with wrong namespaces -->
    </None>
  </ItemGroup>
  <ItemGroup>
    <None Include="..\My.Namespace.MyGlobalResourceProject\MyGlobalImages.Designer.cs">
      <Link>MyGlobalImages.Designer.cs</Link>
      <DependentUpon>MyGlobalImages.resx</DependentUpon>
    </None>
  </ItemGroup>
</Project>

2: Go to the Project-File (I had SDK-Style Projects) where you want to use the global resource. Reference the created file:

<Project Sdk="Microsoft.NET.Sdk">
  <Import Project="$(SolutionDir)\IncludeGlobalResources.targets" />
(...)

3: Now you can just add 2. to every project you want to use the global resource. Also you can extend the file with other global resources easily.

Flax answered 8/5, 2023 at 8:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.