[c#] Resources gebruiken voor images

Pagina: 1
Acties:
  • 1.731 views sinds 30-01-2008
  • Reageer

  • mahi
  • Registratie: Juni 2001
  • Laatst online: 03-10-2025

mahi

God bless GoT

Topicstarter
Ik zou graag een groot aantal afbeeldingen onder brengen in een resource. De resource aanmaken lukt me, maar ze gebruiken is een ander ding. Laten we even volgen extreem simplistisch voorbeeld nemen om een resource aan te maken:
C#:
1
2
3
4
5
ResourceWriter rw = new ResourceWriter("Images.resources");
Image img = Image.FromFile("images/4.png");
rw.AddResource("image", img);
rw.AddResource("text", "test tekst");
rw.Close();
Ik krijg dan een bestand "Images.resource". Als ik even met een hexeditor kijk dan zie ik de afbeelding (hex-code natuurlijk) en de string er duidelijk inzitten. So far so good.

Dan moet ik die resource toevoegen aan m'n project. Hier is al een eerste onduidelijkheid. Alle boeken en tutorials spreken over rechtsklikken op je project en Add -> Existing Item. Maar bedoelen ze daar nu mee rechtsklikken op de solution, of de projectnaam zelf? Ik vermoed weliswaar het tweede, maar even checken voor de zekerheid.

Dan moet ik het geheel natuurlijk implementeren in m'n code. Stel dat ik een form heb met een label "label1" en een picturebox "pictureBox1".
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
public Form1()
{
    ...
    
    System.Resources.ResourceManager rm =
        new System.Resources.ResourceManager("Images",
        System.Reflection.Assembly.GetExecutingAssembly());

    label1.Text = rm.GetString("tekst");
    pictureBox1.Image = (Image)rm.GetObject("image");
    
    ...
}
Dat is nagenoeg letterlijk overgenomen uit de vele tutorials die online te vinden zijn en natuurlijk werkt het hier weer niet... Ik krijg dan volgende melding wanneer ik het project wil uitvoeren.
An unhandled exception of type 'System.Resources.MissingManifestResourceException' occurred in mscorlib.dll

Additional information: Could not find any resources appropriate for the specified culture (or the neutral culture) in the given assembly. Make sure "Images.resources" was correctly embedded or linked into assembly "Test15".
baseName: Images locationInfo: <null> resource file name: Images.resources assembly: Test15, Version=1.0.1977.19125, Culture=neutral, PublicKeyToken=null
Als ik dit goed interpreteer dan is de resource niet gevonden. Maar hoe moet ik deze dan correct toevoegen (ik heb al "add" aan solution en project geprobeerd)? En als ik het niet goed interpreteer, wat is er dan precies mis?

A bus station is where a bus stops. A train station is where a train stops... On my desk I have a workstation.


  • bgever
  • Registratie: April 2002
  • Laatst online: 28-05-2021
Je kunt inderdaad een Resource aan een assembly toevoegen. Alles kan een resource zijn, en gezien je een al bestaand bestand wilt toevoegen moet je "Add existing file" gebruiken. Als dit bestand dan in je project staat, moet je bij de properties instellen dat het bestand "Embedded" moet worden. VS.NET zorgt er dan zelf voor dat dit eerst een resource bestand wordt. Je kunt dus gemakkelijk al bestaande bestanden in je assembly "embedden" op deze manier, zonder hiervan eerst een resource bestand te maken.

Nu kun je het bestand uit de Assembly opvragen middels reflection:
C#:
1
2
3
4
//ontlede expressie voor leesgemak
string res = "Mijn.Default.Project.Namespace.Bestandsnaam.extensie";
Stream s = this.GetType().Assembly.GetManifestResourceStream( res );
Image img = Image.FromStream( s );

Wat ik dus doe, is van het huidige type de Assembly opvragen (aangenomen dat dit type in de zelfde Assembly zit waarin ook de resource zit). Vervolgens roep ik de methode GetManifestResourceStream() aan, waaraan je de resource moet meegeven. Let op dat voor de bestandsnaam de volledige namespace moet staan die je hebt opgegeven in je project properties als default namespace. Mocht je dit bestand in een map in je project hebben gezet, dan moet deze mapnaam ook aan de namespace worden toegevoegd...

Simpel en snel. ;)

  • mahi
  • Registratie: Juni 2001
  • Laatst online: 03-10-2025

mahi

God bless GoT

Topicstarter
Bedankt voor je toevoeging. Ik heb het met wat geknoei toch aan de praat gekregen :)

A bus station is where a bus stops. A train station is where a train stops... On my desk I have a workstation.


  • mahi
  • Registratie: Juni 2001
  • Laatst online: 03-10-2025

mahi

God bless GoT

Topicstarter
Oud topic ondertussen, maar ik heb eindelijk gevonden (puur bij toeval) wat mijn problemen veroorzaakte... Omdat andere mensen misschien met soortgelijke problemen zitten, geef ik hier even de oorzaken die bij mij de eerder vernoemde foutmelding gaven en meteen ook een korte handleiding over hoe correct resources implementeren.

Even een kort voorbeeldje schetsen:

We maken een nieuwe c# Windows applicatie in Visual Studio. Met de rechtermuisknop op je projectnaam in de Solution Explorer kies je Add -> Add New Item -> Local Project Items -> Resources -> Assembly Resource File. Geef een naam op voor de resource en klik op Open. In mijn voorbeeld ga ik 3 resource bestanden aanmaken: Resource.resx (waarin de objecten komen zoals afbeeldingen), Localization.resx (standaard localisatie - Engels), en Localization.nl-NL.resx (Nederlandse localisatie). Voor beide localisatie resources voer ik met de Visual Studio resource editor (gewoon dubbelklikken op de resource in de Solution Explorer) als voorbeeld één entry in:

Localization.resx
name: test
value: Test

Localization.nl-NL.resx
name: test
value: Probeersel

Visual Studio's resource editor kan enkel overweg met tekststrings. Om afbeeldingen toe te voegen moeten we een 3th party programma gebruiken. Zelf gebruik ik Lutz Roeder's .NET Resourcer, een simpel maar effectieve resource editor. Aan Resources.resx voegen we nu een icoon toe met als naam test.ico. Sla de resource op.

Nu moeten we de resources initializeren in de code. Bij de variabeleninitialisatie in je class voeg je toe:
C#:
1
2
private System.Resources.ResourceManager rmObjects;
private System.Resources.ResourceManager rmLocal;
Niet tegenstaande we 3 resources hebben, initializeren we er maar 2. Eentje voor de objecten, en eentje voor de gelocaliseerde strings. We kunnen onderscheid maken tussen de standaard localisatie en de nl-NL localisatie door gebruik te maken van cultures. We maken 2 cultures aan (eentje voor de default en eentje voor nl-NL):
C#:
1
2
private System.Globalization.CultureInfo ciEnglish;
private System.Globalization.CultureInfo ciDutch;
Dit is natuurlijk maar puur initialisatie, dus er moet nog iets met deze variabelen gebeuren. In de constructer van de class zet je:
C#:
1
2
3
4
5
6
rmObjects = new System.Resources.ResourceManager("test.Resource",
    System.Reflection.Assembly.GetExecutingAssembly());
rmLocal = new System.Resources.ResourceManager("test.Localization",
    System.Reflection.Assembly.GetExecutingAssembly());
ciEnglish = new System.Globalization.CultureInfo("en-US");
ciDutch = new System.Globalization.CultureInfo("nl-NL");
Eigenlijk is de en-US culture niet nodig. Ze bestaat in principe niet eens binnen het project. Wanneer een culture niet gevonden wordt (bijvoorbeeld fr-FR welke we geen resource voor hebben gemaakt) valt het programma terug op de standaard resource (in ons geval Localization.resx). Dat is net een van de fijne dingen van localisatie met resources. en-US bestaat ook niet, maar er wordt dan sowieso teruggevallen op de Engelstalige standaard resource. Dus in principe zou je de default resource in ons geval als en-US kunnen zien.

Nu moeten we nog een standaard culture kiezen voor ons programma. Laten we Nederlands nemen...
C#:
1
System.Threading.Thread.CurrentThread.CurrentUICulture = ciDutch;
En klaar is dat. Het resources gedeelte is nu af en zou moeten werken. Als test voegen we volgende regels toe:
C#:
1
2
this.Icon = (System.Drawing.Icon)rmObjects.GetObject("test.ico");
this.Text = (string)rmLocal.GetObject("test");
Wanneer het programma nu uitgevoerd wordt zal de titel van het venster "Probeersel" zijn uit de Nederlandstalige resource, en het icoon van de applicatie is dit uit Resource.resx.

Nog even voor de duidelijkheid het complete programma:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Resources;

namespace test
{
    /// <summary>
    /// Summary description for MainForm.
    /// </summary>
    public class MainForm : System.Windows.Forms.Form
    {
        private System.ComponentModel.IContainer components;

        /************************************/
        private System.Resources.ResourceManager rmObjects;
        private System.Resources.ResourceManager rmLocal;
        private System.Globalization.CultureInfo ciEnglish;
        private System.Globalization.CultureInfo ciDutch;
        /************************************/

        public MainForm()
        {
            //
            // Required for Windows Form Designer support
            //
            InitializeComponent();

            //
            // TODO: Add any constructor code after InitializeComponent call
            //

            /************************************/
            rmObjects = new System.Resources.ResourceManager("test.Resource",
                System.Reflection.Assembly.GetExecutingAssembly());
            rmLocal = new System.Resources.ResourceManager("test.Localization",
                System.Reflection.Assembly.GetExecutingAssembly());
            ciEnglish = new System.Globalization.CultureInfo("en-US");
            ciDutch = new System.Globalization.CultureInfo("nl-NL");
            System.Threading.Thread.CurrentThread.CurrentUICulture = ciDutch;

            this.Icon = (System.Drawing.Icon)rmObjects.GetObject("test.ico");
            this.Text = (string)rmLocal.GetObject("test");
            /************************************/
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            // 
            // MainForm
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(292, 271);
            this.Name = "MainForm";
            this.Text = "MainForm";

        }
        #endregion

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() 
        {
            Application.Run(new MainForm());
        }
    }
}
Wat ging er nu steeds mis want dit lijkt toch wel eenvoudig?

Het is eenvoudig, maar er zijn een paar punten die je in acht moet nemen. Ik had talloze testprojecten gemaakt en vaak werkte het resource gedeelte in den beginne wel goed, maar naarmate ik verder programmeerde kreeg ik ineens die melding. Het was me nooit opgevallen, maar het heeft te maken met de namespace. Ik had de neiging om wanneer een test project (met test-naam en test-namespace) leek te werken alle namen om te zetten naar het project dat ik wilde coden. Dus ook de namespaces werden veranderd. Maar blijkbaar zit de namespace op een of andere manier ook in de resource zelf. Ik veranderde de "test.Resource" (in de code) naar bijvoorbeeld project.Resource en kreeg daarna de foutmeldingen. Maar omdat ik na het veranderen van naam meestal al heel wat code schreef eer ik nog eens testte had ik jammer genoeg nooit de link gelegd. Dus hoewel ik de namespace doorheen m'n hele project veranderde, moesten de resources de oude namespace blijven gebruiken. De juiste oorzaak hiervan weet ik niet, want in de resx resource (geopend met een plain text editor) is geen enkele verwijzing naar de namespace.

Toen ik dat doorhad wilde ik een serieus programma schrijven en toen werkte het weer niet... Na het gips van de muren gebonkt te hebben met m'n hoofd vond ik toch de reden. Ik had een project aangemaakt met een liggend streepje in de naam; firma-client. Omdat een liggend streepje geen geldig teken in een namespace is veranderde visual studio de namespace naar firma_client. Dus met een underscore. Ook de interne hardcoded namespace van de resources gebruikten deze naam met underscore. Maar ik had natuurlijk "firma-client.Resource" met liggend streepje in mijn code staan. Gevolg, zelfde foutmelding. Dat was dus eigenlijk een typo, maar ik had compleet niet opgemerkt dat Visual Studio het streepje had vervangen en gebruikte elders lekker de namespace met het origile streepje.

Dus eigenlijk waren het 2 bijzonder kleine probleempjes die aan de oorzaak van al m'n ellende lagen, maar als je er steeds overheen kijkt dan kan het lang duren eer je een oplossing vindt.

Toch blijven resources iets lastigs. Tijdens m'n zoektochten ben ik een ware stortvloed van mensen die er problemen mee hebben tegengekomen. Velen met dezelfde melding als ikzelf, dus waarschijnlijk ook met een gerelateerd probleem. Ik hoop dat deze post ietwat duidelijkheid brengt.

[ Voor 3% gewijzigd door mahi op 18-08-2005 11:57 ]

A bus station is where a bus stops. A train station is where a train stops... On my desk I have a workstation.


  • gorgi_19
  • Registratie: Mei 2002
  • Laatst online: 27-04 18:17

gorgi_19

Kruimeltjes zijn weer op :9

Waarom voeg je icons niet toe als embedded resource en wil je ze in een resx bestand plaatsen? :)

Digitaal onderwijsmateriaal, leermateriaal voor hbo


  • mahi
  • Registratie: Juni 2001
  • Laatst online: 03-10-2025

mahi

God bless GoT

Topicstarter
Mjah, waarom? Ik ben het gebruik van resources gewend sinds Borland C++ in een ver verleden, maar de daarin ingebouwde resource editor was toch van beduidend betere kwaliteit dan de huidige van Microsoft :)

A bus station is where a bus stops. A train station is where a train stops... On my desk I have a workstation.


  • gorgi_19
  • Registratie: Mei 2002
  • Laatst online: 27-04 18:17

gorgi_19

Kruimeltjes zijn weer op :9

Dat je niet moeilijk moet doen met aparte / losse programma's en gewoon in Visual Studio "Embedded Resource" kan selecteren :)

Digitaal onderwijsmateriaal, leermateriaal voor hbo

Pagina: 1