以程式設計方式使用 .resx 檔案

注意

本文適用於 .NET Framework。 如需適用於 .NET 5+ (包括 .NET Core) 的資訊,請參閱 .resx 檔案中的資源

因為 XML 資源 (.resx) 檔必須由妥善定義的 XML 組成,而其中所包含的標頭,需遵循特定結構描述且後面接著名稱/值組的資料,可能您會認為以手動方式建立這些檔案容易出錯。 另一種方法是,您可以使用 .NET Class Library 中的型別和成員,透過程式設計的方式建立 .resx 檔案。 您也可以使用 .NET Class Library 擷取儲存在 .resx 檔案中的資源。 本文說明如何使用 System.Resources 命名空間中的型別和成員,來處理 .resx 檔。

本文探討的是含有資源之 XML (.resx) 檔的運用。 如需使用已內嵌於組件的二進位資源檔相關資訊,請參閱 ResourceManager

警告

除了透過程式設計之外,還有其他運用 .resx 檔的方法。 當您將資源檔新增到 Visual Studio 專案時,Visual Studio 會提供介面讓您建立和維護 .resx 檔案,並於編譯時間自動將 .resx 檔案轉換成 .resources 檔案。 您也可以使用文字編輯器直接操作 .resx 檔。 不過,為避免損毀檔案,請小心別更動任何儲存在檔案中的二進位資訊。

建立.resx 檔案

您可以使用 System.Resources.ResXResourceWriter 類別執行下列步驟,以程式設計方式建立 .resx 檔:

  1. 呼叫 ResXResourceWriter 方法並提供 .resx 檔的名稱,具現化 ResXResourceWriter(String) 物件。 檔案名稱必須包含 .resx 這個副檔名。 如果您是在 ResXResourceWriter 區塊中具現化 using 物件,則您不必非得呼叫步驟 3 的 ResXResourceWriter.Close 方法。

  2. 針對您要加入檔案的每個資源呼叫 ResXResourceWriter.AddResource 方法。 使用此方法的多載功能加入字串、物件和二進位檔 (位元組陣列) 資料。 如果資源是物件,它必須可以序列化。

  3. 呼叫 ResXResourceWriter.Close 方法以產生資源檔並釋放所有資源。 如果 ResXResourceWriter 物件是在 using 區塊中建立,則資源會寫入 .resx 檔,且 ResXResourceWriter 物件所使用的資源會在 using 區塊的結尾釋放。

產生的 .resx 檔具有適當的標頭以及 data 方法所加入之每個資源的 ResXResourceWriter.AddResource 標記。

警告

請勿使用資源檔儲存密碼、安全機密資訊或私用資料。

下列範例會建立名為 CarResources.resx 的.resx 檔,其中儲存六個字串、一個圖示和兩個應用程式定義的物件 (兩個 Automobile 物件)。 範例中所定義和具現化的 Automobile 類別,會標記為 SerializableAttribute 屬性。

using System;
using System.Drawing;
using System.Resources;

[Serializable()] public class Automobile
{
   private string carMake;
   private string carModel;
   private int carYear;
   private int carDoors;
   private int carCylinders;

   public Automobile(string make, string model, int year) :
                     this(make, model, year, 0, 0)
   { }

   public Automobile(string make, string model, int year,
                     int doors, int cylinders)
   {
      this.carMake = make;
      this.carModel = model;
      this.carYear = year;
      this.carDoors = doors;
      this.carCylinders = cylinders;
   }

   public string Make {
      get { return this.carMake; }
   }

   public string Model {
      get {return this.carModel; }
   }

   public int Year {
      get { return this.carYear; }
   }

   public int Doors {
      get { return this.carDoors; }
   }

   public int Cylinders {
      get { return this.carCylinders; }
   }
}

public class Example
{
   public static void Main()
   {
      // Instantiate an Automobile object.
      Automobile car1 = new Automobile("Ford", "Model N", 1906, 0, 4);
      Automobile car2 = new Automobile("Ford", "Model T", 1909, 2, 4);
      // Define a resource file named CarResources.resx.
      using (ResXResourceWriter resx = new ResXResourceWriter(@".\CarResources.resx"))
      {
         resx.AddResource("Title", "Classic American Cars");
         resx.AddResource("HeaderString1", "Make");
         resx.AddResource("HeaderString2", "Model");
         resx.AddResource("HeaderString3", "Year");
         resx.AddResource("HeaderString4", "Doors");
         resx.AddResource("HeaderString5", "Cylinders");
         resx.AddResource("Information", SystemIcons.Information);
         resx.AddResource("EarlyAuto1", car1);
         resx.AddResource("EarlyAuto2", car2);
      }
   }
}
Imports System.Drawing
Imports System.Resources

<Serializable()> Public Class Automobile
    Private carMake As String
    Private carModel As String
    Private carYear As Integer
    Private carDoors AS Integer
    Private carCylinders As Integer

    Public Sub New(make As String, model As String, year As Integer)
        Me.New(make, model, year, 0, 0)
    End Sub

    Public Sub New(make As String, model As String, year As Integer,
                   doors As Integer, cylinders As Integer)
        Me.carMake = make
        Me.carModel = model
        Me.carYear = year
        Me.carDoors = doors
        Me.carCylinders = cylinders
    End Sub

    Public ReadOnly Property Make As String
        Get
            Return Me.carMake
        End Get
    End Property

    Public ReadOnly Property Model As String
        Get
            Return Me.carModel
        End Get
    End Property

    Public ReadOnly Property Year As Integer
        Get
            Return Me.carYear
        End Get
    End Property

    Public ReadOnly Property Doors As Integer
        Get
            Return Me.carDoors
        End Get
    End Property

    Public ReadOnly Property Cylinders As Integer
        Get
            Return Me.carCylinders
        End Get
    End Property
End Class

Module Example
    Public Sub Main()
        ' Instantiate an Automobile object.
        Dim car1 As New Automobile("Ford", "Model N", 1906, 0, 4)
        Dim car2 As New Automobile("Ford", "Model T", 1909, 2, 4)
        ' Define a resource file named CarResources.resx.
        Using resx As New ResXResourceWriter(".\CarResources.resx")
            resx.AddResource("Title", "Classic American Cars")
            resx.AddResource("HeaderString1", "Make")
            resx.AddResource("HeaderString2", "Model")
            resx.AddResource("HeaderString3", "Year")
            resx.AddResource("HeaderString4", "Doors")
            resx.AddResource("HeaderString5", "Cylinders")
            resx.AddResource("Information", SystemIcons.Information)
            resx.AddResource("EarlyAuto1", car1)
            resx.AddResource("EarlyAuto2", car2)
        End Using
    End Sub
End Module

提示

您也可以使用 Visual Studio 建立 .resx 檔案。 Visual Studio 會在編譯時期使用 資源檔產生器 (Resgen.exe) 將 .resx 檔轉換成二進位資源 (.resources) 檔,並將其內嵌在應用程式組件或附屬組件中。

您無法將 .resx 檔內嵌於執行階段可執行檔,或將其編譯至附屬組件中。 您必須使用 資源檔產生器 (Resgen.exe)將 .resx 檔轉換成二進位資源 (.resources) 檔。 產生的 .resources 檔接著可內嵌於應用程式組件或附屬組件中。 如需詳細資訊,請參閱建立資源檔

列舉資源

在某些情況下,您可能會想從 .resx 檔擷取所有資源,而非特定資源。 若要這樣做,您可以使用 System.Resources.ResXResourceReader 類別,此類別會在 .resx 檔中提供所有資源的列舉值。 System.Resources.ResXResourceReader 類別會實作 IDictionaryEnumerator,並傳回 DictionaryEntry 物件,表示每個在迴圈中反覆運算的特定資源。 其 DictionaryEntry.Key 屬性會傳回資源的索引鍵,且其 DictionaryEntry.Value 屬性會傳回資源的值。

下列範例會針對上一個範例中所建立的 CarResources.resx 檔建立 ResXResourceReader 物件,並透過資源檔逐一查看。 它會將資源檔中所定義的兩個 Automobile 物件加入 System.Collections.Generic.List<T> 物件中,並將六個字串的其中五個加入 SortedList 物件。 SortedList 物件中的值會轉換到用來對主控台顯示欄位標題的參數陣列。 Automobile 屬性值也會顯示到主控台。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Resources;

public class Example
{
   public static void Main()
   {
      string resxFile = @".\CarResources.resx";
      List<Automobile> autos = new List<Automobile>();
      SortedList headers = new SortedList();

      using (ResXResourceReader resxReader = new ResXResourceReader(resxFile))
      {
         foreach (DictionaryEntry entry in resxReader) {
            if (((string) entry.Key).StartsWith("EarlyAuto"))
               autos.Add((Automobile) entry.Value);
            else if (((string) entry.Key).StartsWith("Header"))
               headers.Add((string) entry.Key, (string) entry.Value);
         }
      }
      string[] headerColumns = new string[headers.Count];
      headers.GetValueList().CopyTo(headerColumns, 0);
      Console.WriteLine("{0,-8} {1,-10} {2,-4}   {3,-5}   {4,-9}\n",
                        headerColumns);
      foreach (var auto in autos)
         Console.WriteLine("{0,-8} {1,-10} {2,4}   {3,5}   {4,9}",
                           auto.Make, auto.Model, auto.Year,
                           auto.Doors, auto.Cylinders);
   }
}
// The example displays the following output:
//       Make     Model      Year   Doors   Cylinders
//
//       Ford     Model N    1906       0           4
//       Ford     Model T    1909       2           4
Imports System.Collections
Imports System.Collections.Generic
Imports System.Resources

Module Example
    Public Sub Main()
        Dim resxFile As String = ".\CarResources.resx"
        Dim autos As New List(Of Automobile)
        Dim headers As New SortedList()

        Using resxReader As New ResXResourceReader(resxFile)
            For Each entry As DictionaryEntry In resxReader
                If CType(entry.Key, String).StartsWith("EarlyAuto") Then
                    autos.Add(CType(entry.Value, Automobile))
                Else If CType(entry.Key, String).StartsWith("Header") Then
                    headers.Add(CType(entry.Key, String), CType(entry.Value, String))
                End If
            Next
        End Using
        Dim headerColumns(headers.Count - 1) As String
        headers.GetValueList().CopyTo(headerColumns, 0)
        Console.WriteLine("{0,-8} {1,-10} {2,-4}   {3,-5}   {4,-9}",
                          headerColumns)
        Console.WriteLine()
        For Each auto In autos
            Console.WriteLine("{0,-8} {1,-10} {2,4}   {3,5}   {4,9}",
                              auto.Make, auto.Model, auto.Year,
                              auto.Doors, auto.Cylinders)
        Next
    End Sub
End Module
' The example displays the following output:
'       Make     Model      Year   Doors   Cylinders
'       
'       Ford     Model N    1906       0           4
'       Ford     Model T    1909       2           4

擷取特定資源

除了列舉 .resx 檔中的項目之外,您也可以使用 System.Resources.ResXResourceSet 類別依名稱擷取特定的資源。 ResourceSet.GetString(String) 方法會擷取具名字串資源的值。 ResourceSet.GetObject(String) 方法會擷取具名物件或二進位資料的值。 方法會傳回物件,接著該物件必須 (在 C# 中) 進行轉換或 (在 Visual Basic 中) 轉換成適當型別的物件。

下列範例會依其資源名稱擷取某個表單的標題字串和圖示。 它也會擷取前一個範例中所使用之應用程式定義的 Automobile 物件,並在 DataGridView 控制項顯示這些物件。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Resources;
using System.Windows.Forms;

public class CarDisplayApp : Form
{
   private const string resxFile = @".\CarResources.resx";
   Automobile[] cars;

   public static void Main()
   {
      CarDisplayApp app = new CarDisplayApp();
      Application.Run(app);
   }

   public CarDisplayApp()
   {
      // Instantiate controls.
      PictureBox pictureBox = new PictureBox();
      pictureBox.Location = new Point(10, 10);
      this.Controls.Add(pictureBox);
      DataGridView grid = new DataGridView();
      grid.Location = new Point(10, 60);
      this.Controls.Add(grid);

      // Get resources from .resx file.
      using (ResXResourceSet resxSet = new ResXResourceSet(resxFile))
      {
         // Retrieve the string resource for the title.
         this.Text = resxSet.GetString("Title");
         // Retrieve the image.
         Icon image = (Icon) resxSet.GetObject("Information", true);
         if (image != null)
            pictureBox.Image = image.ToBitmap();

         // Retrieve Automobile objects.
         List<Automobile> carList = new List<Automobile>();
         string resName = "EarlyAuto";
         Automobile auto;
         int ctr = 1;
         do {
            auto = (Automobile) resxSet.GetObject(resName + ctr.ToString());
            ctr++;
            if (auto != null)
               carList.Add(auto);
         } while (auto != null);
         cars = carList.ToArray();
         grid.DataSource = cars;
      }
   }
}
Imports System.Collections.Generic
Imports System.Drawing
Imports System.Resources
Imports System.Windows.Forms

Public Class CarDisplayApp : Inherits Form
    Private Const resxFile As String = ".\CarResources.resx"
    Dim cars() As Automobile

    Public Shared Sub Main()
        Dim app As New CarDisplayApp()
        Application.Run(app)
    End Sub

    Public Sub New()
        ' Instantiate controls.
        Dim pictureBox As New PictureBox()
        pictureBox.Location = New Point(10, 10)
        Me.Controls.Add(pictureBox)
        Dim grid As New DataGridView()
        grid.Location = New Point(10, 60)
        Me.Controls.Add(grid)

        ' Get resources from .resx file.
        Using resxSet As New ResXResourceSet(resxFile)
            ' Retrieve the string resource for the title.
            Me.Text = resxSet.GetString("Title")
            ' Retrieve the image.
            Dim image As Icon = CType(resxSet.GetObject("Information", True), Icon)
            If image IsNot Nothing Then
                pictureBox.Image = image.ToBitmap()
            End If

            ' Retrieve Automobile objects.  
            Dim carList As New List(Of Automobile)
            Dim resName As String = "EarlyAuto"
            Dim auto As Automobile
            Dim ctr As Integer = 1
            Do
                auto = CType(resxSet.GetObject(resName + ctr.ToString()), Automobile)
                ctr += 1
                If auto IsNot Nothing Then carList.Add(auto)
            Loop While auto IsNot Nothing
            cars = carList.ToArray()
            grid.DataSource = cars
        End Using
    End Sub
End Class

將 .resx 檔案轉換成二進位 .resources 檔案

將 .resx 檔轉換成內嵌二進位資源 (.resources) 檔案有很大的好處。 雖然 .resx 檔在應用程式開發過程易於閱讀及維護,但是這種檔案很少會包含在已完成的應用程式中。 如果它們經由應用程式散發,就會從應用程式執行檔及其隨附的程式庫中分離出來,成為單獨的檔案。 相較之下,.resources 檔則會內嵌在應用程式執行檔或其隨附的組件中。 此外,以當地語系化的應用程式而言,若要在執行階段仰賴 .resx 檔,就得靠開發人員負責處理資源後援。 與之相反的是,假如已經建立一組含有內嵌 .resources 檔的附屬組件,Common Language Runtime 就會負責處理資源後援程序。

若要將 .resx 檔轉換成 .resources 檔,您可使用資源檔產生器 (Resgen.exe),其基本語法如下:

 resgen.exe .resxFilename

結果是一個二進位資源檔,具有與 .resx 檔相同的根檔名和 .resources 檔的副檔名。 這個檔案可以在編譯時間編譯到執行檔或程式庫中。 如果您使用的是 Visual Basic 編譯器,請使用下列語法在應用程式的執行檔中內嵌 .resources 檔:

vbc filename .vb -resource: .resourcesFilename

如果您使用的是 C#,其語法如下:

 csc filename .cs -resource: .resourcesFilename

您也可以利用 組件連結器 (AL.exe) (al.exe),將 .resources 檔內嵌於附屬組件中,其基本語法如下:

al resourcesFilename -out: assemblyFilename

另請參閱