W poprzednim wpisie pisałem o tym, że zamiast wyznaczać ścieżki do obrazków, lepiej umieścić je w zasobach. Temat wydawał mi się prosty, ale w komentarzach pojawiła się odrobinę odmienna opinia:
"Nie, obrazków nie trzyma się w zasobach. Ikony, czasem, jak potrzeba, jeden, dwa obrazki, ale nie więcej. One są za duże - EXE-k puchnie, robi się bałagan, trzeba rekompilować, by zmienić obrazek, a i krótsze nie jest. Tą linijkę równie dobrze można zapisać jako:
ImageBrush brush = new ImageBrush(new BitmapImage(new Uri("Images/test.png", UriKind.Relative)));
Można to wpakować w jakąś statyczną klasę helpera, by nie pisać nie wiadomo ile razy."
W tym wpisie postaram się wyjaśnić i uzupełnić informacje związane z zasobami w WPF. Moim celem nie jest kompletna negacja przedstawionej opinii, ale przyjrzyjmy się, co daje nam .NET Framework i jak to można wykorzystać, by nasza aplikacja była lepsza.
Zacznijmy od prostego stwierdzenia: są dwa typy zasobów:
-
Zasoby w sensie XAML
-
Zasoby w sensie plików osadzonych w jakimś assembly
Zasoby (resources) w XAML
Zauważmy, że WPF pozwala nam definiować zasoby w XAML, można to zrobić bezpośrednio w kodzie XAML okna lub kontrolki (w poprzednim wpisie właśnie był taki przykład) lub lepiej zasób zdefiniować w pliku App.xaml (wspólnym dla całej aplikacji) lub najlepiej w ResourceDictionary.
Zobaczmy poniższy przykład, w którym zasób zadeklarowano w pliku App.xaml:
<Application x:Class="WpfApplication_resources_tests.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> <BitmapImage x:Key="MyAppImageSource" UriSource="./ImagesToBeCopied/logo_zielone_tlo.jpg" /> <ImageBrush x:Key="MyAppImageBrush" ImageSource="{StaticResource ResourceKey=MyAppImageSource}"/> </Application.Resources> </Application>
Zauważmy, że obrazek nie został dodany do assembly, a tylko skopiowany do katalogu aplikacji:
I wykorzystano go poprzez:
<Window x:Class="WpfApplication_resources_tests.Window2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window2" Height="300" Width="300"> <Grid> <Label Background="{StaticResource ResourceKey=MyAppImageBrush}" Width="50" Height="50" VerticalAlignment="Top" HorizontalAlignment="Left"/> <Viewport3D Name="mainViewport"> <Viewport3D.Camera> <PerspectiveCamera FarPlaneDistance="100" LookDirection="-1,-1,-1" UpDirection="0,1,0" NearPlaneDistance="1" Position="3,3,3" FieldOfView="70" /> </Viewport3D.Camera> <ModelVisual3D> <ModelVisual3D.Content> <DirectionalLight Color="White" Direction="-1,-1,-1" /> </ModelVisual3D.Content> </ModelVisual3D> <ModelVisual3D> <ModelVisual3D.Content> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D Positions="-2 -2 0 2 -2 0 -2 2 0 2 2 0" Normals="0 0 1 0 0 1 0 0 1 0 0 1" TextureCoordinates="0 1 1 1 0 0 1 0 " TriangleIndices="0 1 2 1 3 2" /> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial Brush="{StaticResource ResourceKey=MyAppImageBrush}" /> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> </ModelVisual3D> </Viewport3D> </Grid> </Window>
W wyniku otrzymamy okno dokładnie takie jak poprzednio. Oczywiście można to zrobić przez ResourceDictionary (i jest to sposób zalecany przez Ms). Zaletą takiego podejścia są:
-
Mamy jedno miejsce, w którym nastąpiła deklaracja zasobu.
-
Łatwo możemy się do zasobu odwołać.
-
Jest to standardowy sposób i każdy, kto programuje w WPF, będzie wiedział jak z tego skorzystać.
-
Nie tworzymy żadnych „helperów”, bo nie ma takiej potrzeby. Nie wynajdujemy koła na nowo!!
Zasoby (resources) w assembly
Dodatkowe pliki dla aplikacji można trzymać bezpośrednio w assembly. Pisałem o tym już wcześniej w artykule: "Prosta obsługa Resource’ów aplikacji .NET (C#)". Teraz skupmy się jednak na wykorzystaniu tego w WPF. Chciałbym tutaj podkreślić, że nie jestem fanem trzymania informacji w plikach EXE i nie zalecam tam tego robić! Nie jest jednak dobrym podejściem trzymanie grafik, w taki sposób, że każda grafika (lub dowolny inny zasób) znajduje się w osobnym pliku. Przecież nie chcemy narażać się na potencjalne problemy z deployment'em – trzeba pamiętać, co było dodawane. Zastanówmy się również, czy chcemy pozwolić każdemu (nawet niedoświadczonemu) użytkownikowi na zmienianie naszej aplikacji (prawie każdy potrafi podmienić obrazek w jakimś katalogu, czy tego na prawdę chcemy?). Do zgromadzenia naszych zasobów lepiej wykorzystać odrębne assembly. A następnie można w XAML zdefiniować zasoby przy pomocy adresacji PACK.
<BitmapImage x:Key="MyImageSource" UriSource= "pack://application:,,,/MyResourcesLibrary;component/puzzle.png" />
Wspomniane assembly nie musi być nawet dodane do projektu jako referencja (można je podmieniać później bez rekompilacji). To jak łatwo to można zrobić pokazuje poniższy rysunek:
Literatura
Polecam również przeczytać więcej na ten temat:
-
MSDN: WPF Application Resource, Content, and Data Files: http://msdn.microsoft.com/en-us/library/aa970494.aspx
-
MSDN: Pack URIs in WPF: http://msdn.microsoft.com/en-us/library/aa970069.aspx
-
Open Packaging Conventions (OPC, tylko trochę inne niż do tej pory wspominane na tym blogu): http://msdn.microsoft.com/en-us/windows/hardware/gg463375.aspx
Pamiętajcie, że lepiej wykorzystać coś, co ktoś już dla nas przygotował, są zasoby – korzystajmy z nich, nie wynajdujmy koła na nowo!
PS
Mam nadzieję, że nie uraziłem nikogo. Jeżeli tak, to przepraszam, ale nie taki był mój zamiar.
Wielkie dzięki.. miałem trochę kłopotu z ikonkami w programie, gdy uruchamiałem go z instalki. Trafiłem na ten wpis i wszystko stalo sie proste. Wielkie dzieki jeszcze raz :)
OdpowiedzUsuńCo zrobić, kiedy VS2013 nie widzi mojego assembly z zasobami, tzn wyświetlanie ostrzeżenie (po najechaniu na linię z odwołaniem 'UriSource="pack://application:,,,' w xaml)
OdpowiedzUsuń"Could not load file or assembly 'MojeZasoby, Culture=Neutral' or one of its dependencies."?
Zrobiłem wszystko jak na instrukcji z obrazka oprócz "Copied in Build event (not referenced)", bo nie mam pojęcia jak to zrobić.