2013-05-07 28 views
15

Jak utworzyć identyfikator URI opakowania dla obrazu znajdującego się w pliku zasobów?Identyfikator URI opakowania dla obrazu osadzonego w pliku resx

mam zespół o nazwie MyAssembly.Resources.dll, ma folder o nazwie Obrazy, to tam jest plik zasobu nazywany Assets.resx. Ten plik zasobów zawiera mój obraz (o nazwie MyImage.png). Linia kodu mam to:

uri = new Uri("pack://application:,,,/MyAssembly.Resources,Culture=neutral,PublicKeyToken=null;component/Images/Assets/MyImage.png"); 

Jednak gdy próbuję dostarczyć ten URI do konstruktora nowego BitmapImage dostaję IOException z komunikatem

nie może zlokalizować zasobów „images/aktywa /myimage.png ".

Zauważ, że mam inne luźne obrazy w tym samym zespole, który można pobrać w porządku za pomocą pakietu URI, te obrazy mają ich działanie kompilacji ustawiony Zasób ale nie są one osadzone w pliku RESX. Czy powinienem dołączyć nazwę pliku resx do ścieżki?

(Szukam osadzenia obrazów w plikach resx, dzięki czemu mogę wykorzystać ustawienia kultury interfejsu użytkownika w celu pobrania odpowiedniego obrazu (obraz zawiera tekst)).

+1

Co sprawia, że ​​myślisz, że to możliwe przy użyciu 'pakiet:' schemat? Jest to związane ze znormalizowanymi specyfikacjami Open Packaging Conventions. Tak więc uri wskazuje zasoby pakietu aplikacji, a nie zasoby osadzone .NET. Możliwe, że jest to możliwe z innym schematem/protokołem (możesz przekierować ten niestandardowy protokół do osadzonych zasobów, coś w rodzaju 'new Uri (" resx: // blabla ")'). –

+0

@SimonMourier Myślałem, że musi istnieć sposób, aby to zrobić, ponieważ możesz łączyć łańcuchy znaków w plikach resx z XAML, więc byłoby mało prawdopodobne, że nie byłoby sposobu na dotarcie do innych typów zasobów osadzonych w tym samym pliku, oraz URI jest sposobem opisywania ścieżki do zasobu. Jeśli masz sugestię dotyczącą używania niestandardowego protokołu, chętnie go wysłucham. Luźne (nietekstowe) obrazy są przywoływane przy użyciu identyfikatora URI opakowania, więc chciałem zachować spójność, jeśli to w ogóle możliwe. – slugster

Odpowiedz

15

Nie sądzę, jest to możliwe za pomocą „Akumulator” schemat protokołu. Ten protokół jest powiązany ze znormalizowanymi specyfikacjami Open Packaging Conventions (http://tools.ietf.org/id/draft-shur-pack-uri-scheme-05.txt dla wskaźników). Tak więc pakiet uri wskazuje zasoby pakietu aplikacji (lub ich części w OPC), a nie zasoby osadzone .NET.

Można jednak zdefiniować własny schemat, na przykład "resx" i użyć go w komponencie WPF. Nowe schematy Uri dla takich zastosowań można zdefiniować za pomocą WebRequest.RegisterPrefix.

Oto przykład oparty na małym projekcie aplikacji Wpf o nazwie "WpfApplication1". Ta aplikacja ma zdefiniowany plik Resource1.resx (i prawdopodobnie inne zlokalizowane odpowiednie pliki Resource1, na przykład Resource1.fr-FR.resx, na przykład francuski). Każdy z tych plików ResX definiuje zasób obrazu o nazwie "img" (zauważ, że ta nazwa nie jest taka sama jak nazwa pliku obrazu, na którym opiera się zasób).

Oto MainWindow.xaml:

<Window x:Class="WpfApplication1.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Title="MainWindow" Height="350" Width="525"> 
    <Image Source="resx:///WpfApplication1.Resource1/img" /> 
</Window> 

Format uri to:

resx://assembly name/resource set name/resource name 

i nazwa zespołu jest opcjonalne, więc

resx:///resource set name/resource name 

jest również ważne i punkt do zasobów w głównym zespole (moja próbka używa tego)

To jest kod, który obsługuje go w App.xaml.cs lub gdzieś indziej, trzeba zarejestrować nowego schematu:

public partial class App : Application 
{ 
    protected override void OnStartup(StartupEventArgs e) 
    { 
     ResXWebRequestFactory.Register(); 
     base.OnStartup(e); 
    } 
} 

i wdrożenie schematu:

public sealed class ResXWebRequestFactory : IWebRequestCreate 
{ 
    public const string Scheme = "resx"; 
    private static ResXWebRequestFactory _factory = new ResXWebRequestFactory(); 

    private ResXWebRequestFactory() 
    { 
    } 

    // call this before anything else 
    public static void Register() 
    { 
     WebRequest.RegisterPrefix(Scheme, _factory); 
    } 

    WebRequest IWebRequestCreate.Create(Uri uri) 
    { 
     return new ResXWebRequest(uri); 
    } 

    private class ResXWebRequest : WebRequest 
    { 
     public ResXWebRequest(Uri uri) 
     { 
      Uri = uri; 
     } 

     public Uri Uri { get; set; } 

     public override WebResponse GetResponse() 
     { 
      return new ResXWebResponse(Uri); 
     } 
    } 

    private class ResXWebResponse : WebResponse 
    { 
     public ResXWebResponse(Uri uri) 
     { 
      Uri = uri; 
     } 

     public Uri Uri { get; set; } 

     public override Stream GetResponseStream() 
     { 
      Assembly asm; 
      if (string.IsNullOrEmpty(Uri.Host)) 
      { 
       asm = Assembly.GetEntryAssembly(); 
      } 
      else 
      { 
       asm = Assembly.Load(Uri.Host); 
      } 

      int filePos = Uri.LocalPath.LastIndexOf('/'); 
      string baseName = Uri.LocalPath.Substring(1, filePos - 1); 
      string name = Uri.LocalPath.Substring(filePos + 1); 

      ResourceManager rm = new ResourceManager(baseName, asm); 
      object obj = rm.GetObject(name); 

      Stream stream = obj as Stream; 
      if (stream != null) 
       return stream; 

      Bitmap bmp = obj as Bitmap; // System.Drawing.Bitmap 
      if (bmp != null) 
      { 
       stream = new MemoryStream(); 
       bmp.Save(stream, bmp.RawFormat); 
       bmp.Dispose(); 
       stream.Position = 0; 
       return stream; 
      } 

      // TODO: add other formats 
      return null; 
     } 
    } 
} 
+0

uczysz się czegoś każdego dnia :) – NSGaga

+0

Dzięki Simon. Miałem kilka opcji awaryjnych, ale twoja opcja daje mi prawie to, na co liczyłem - dzięki temu mogę zachować spójność w kodzie wymagającym tego kodu, nie muszę komplikować kodu w zależności od tego, czy jest to luźno zapakowany obraz lub osadzony w resx. – slugster

4

Istnieją dwa sposoby "osadzenia" zasobu w zespole. Windows Forms używa akcji budowania Embedded Resource. Program WPF oczekuje, że zasoby zawarte w złożeniach zostaną oznaczone jako Działanie kompilacji Resource.

Podczas korzystania z edytora Resx w programie Visual Studio w celu dodania obrazu oznacza on osadzony zasób. Ponadto zapisuje go jako typ System.Drawing.Bitmap. WPF oczekuje typu System.Windows.Media.ImageSource.

Jeśli masz dissembler (np. ILSpy), możesz sprawdzić wpływ ustawienia różnych akcji kompilacji na pliki.

Próbka ImagesLib projekt

Oto zrzut ekranu z projektem z dwoma obrazami. Jest oczywiste, że z nazwy, cat_embedded.jpg używa działania budowania Embedded Resource i cat_resource.jpg używa działania budowania cat_resource.jpg.

enter image description here

To, jak wyglądają w ILSpy.

enter image description here

Zobacz jak plik cat_resource.jpg jest w sekcji ImageLib.g.resources? Właśnie tam WPF szuka zasobów. Ścieżka do pliku jest częścią nazwy zasobu (images/cat_resource.jpg). Tak więc podczas korzystania ścieżkę jak:

var uri = new Uri("pack://application:,,,/ImageLib;component/Images/cat_resource.jpg"); 

podać ścieżkę dopasowania po słowie ;component.

Drugi plik jpg znajduje się w innym miejscu w zespole i używa kropek w nazwie (ImageLib.Images.cat_embedded.jpg).

Możesz wypróbować wiele permutacji tego ciągu, aby spróbować uzyskać obraz cat_embedded.jpg, ale WPF go nie znajdzie.

RESX Editor

Oto kolejny projekt, który ma dwa obrazy, jeden oznaczony jako zasób i dodany przez redaktora RESX.

enter image description here

I tu jest zdemontowany ekranu.

enter image description here

Jak widać, RESX obrazu przy użyciu tej samej lokalizacji URI jako wcześniejszym wbudowanego przykład obrazu. Pojawia się w twoim przypadku, nie będziesz w stanie uzyskać obrazów z pliku resx za pomocą URI Pack.

Localization

Z tego, co pan powiedział w swoim pytaniu, co staramy się osiągnąć jest lokalizacja obrazów prawda?

Czy obejrzałeś ten artykuł MSDN?

WPF Globalization and Localization Overview

+0

+1, aby uzyskać szczegółowe objaśnienie elementów wewnętrznych. –

3

Jak słusznie zauważył Walt, to, co otrzymasz z pliku resx, to System.Drawing.Bitmap. Musi więc zostać przekonwertowany na System.Windows.Media.ImageSource lub podtyp.

Nie jestem pewien, czy to spadnie pod czas marnotrawstwa dla ciebie, ponieważ nie wykorzystuje identyfikatora URI, ale oto jak mogę uzyskać obrazy z plików resx w innej bibliotece. Używam a simple proxy, ponieważ plik projektanta Resx eksponuje tylko wewnętrzny konstruktor (nawet jeśli klasa jest publiczna), a następnie zdefiniuj ValueConverter, który zapewni źródło obrazu.

enter image description here

<Window x:Class="WpfApplication1.MainWindow" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:local="clr-namespace:WpfApplication1" 
      xmlns:resx="clr-namespace:MyAssembly.Resources;assembly=MyAssembly.Resources" 
      Title="MainWindow" Height="350" Width="525"> 

    <Window.Resources> 
     <resx:AssetsProxy x:Key="Assets" /> 
     <resx:BitmapToImageSourceConverter x:Key="BitmapConverter" /> 
    </Window.Resources> 

    <Image Source="{Binding myimage, Source={StaticResource Assets}, Converter={StaticResource BitmapConverter}}" /> 
</Window> 

AssetsProxy:

namespace MyAssembly.Resources 
{ 
    public class AssetsProxy : Images.Assets 
    { 
     public AssetsProxy() : base() { } 
    } 
} 

bitmapy do konwersji ImageSource:

using System; 
using System.Drawing.Imaging; 
using System.Globalization; 
using System.IO; 
using System.Windows.Data; 
using System.Windows.Media.Imaging; 

namespace MyAssembly.Resources 
{ 
    /// <summary> 
    /// Converts a BitmapImage, as provided by a resx resource, into an ImageSource/BitmapImage 
    /// </summary> 
    public class BitmapToImageSourceConverter : IValueConverter 
    { 
     public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
     { 
      BitmapImage bitmapImage = null; 
      if (value is System.Drawing.Image) 
      { 
       bitmapImage = ((System.Drawing.Image)value).ToBitmapImage(); 
      } 
      return bitmapImage; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
     { 
      throw new NotImplementedException(); 
     } 
    } 

    public static class BitmapExtensions 
    { 
     /// <summary> 
     /// Converts the System.Drawing.Image to a System.Windows.Media.Imaging.BitmapImage 
     /// </summary> 
     public static BitmapImage ToBitmapImage(this System.Drawing.Image bitmap) 
     { 
      BitmapImage bitmapImage = null; 
      if (bitmap != null) 
      { 
       using (MemoryStream memory = new MemoryStream()) 
       { 
        bitmapImage = new BitmapImage(); 
        bitmap.Save(memory, ImageFormat.Png); 
        memory.Position = 0; 
        bitmapImage.BeginInit(); 
        bitmapImage.StreamSource = memory; 
        bitmapImage.CacheOption = BitmapCacheOption.OnLoad; 
        bitmapImage.EndInit(); 
       } 
      } 
      return bitmapImage; 
     } 
    } 
}