János's profileJános JankaPhotosBlogListsMore Tools Help

Blog


    April 29

    Kézírás felismerés a WPF-ben


    Korábbi igéreteimhez hűen, itt az idő, hogy megmutassam milyen egyszerű a kézírás-felismerés használata a WPF-ben (köszönet Velvárt Andrásnak és a Response Kft.-nek az ötletért). Én ugyan nem foglalkozom Tablet PC fejlesztéssel, de ezt érdekesnek találtam, így hát muszály voltam kipróbálni :).

    Először is készítsünk egy új WPF alkalmazást. Adjuk hozzá a következő két assemblyt referenciaként:

    IALoader.dll - C:\Program Files\Reference Assemblies\Microsoft\Tablet PC\v1.7\IAWinFX.dll
    IAWinFX.dll - C:\Program Files\Reference Assemblies\Microsoft\Tablet PC\v1.7\IAWinFX.dll

    Következő lépés, hogy tegyünk le egy InkCanvas-t. Az InkCanvas képes stroke objektumokat kezelni. Ezen kívül (ami így se túl sok), még pár funkciót kitehetünk az InkCanvas mellé a könnyebb kezelés érdekében. Megint jobb lesz ha beillesztem ide az egészet és a két szemünkkel láthatjuk azt, hogy milyen egyszerű ez:

    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        xmlns:Ink="clr-namespace:System.Windows.Ink;assembly=PresentationCore"    
        xmlns:WPFInkApi="clr-namespace:WPFInkApi"        
        x:Class="WPFInkApi.WinMain"
        Title="WPF InkCanvas" SizeToContent="Width" Height="300"
        WindowStartupLocation="CenterScreen">
    
        <Grid>
            <!-- a grid felosztása három sorra -->
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            
            <!-- az első sorba kerül a "menü" -->
            <StackPanel
                Grid.Row="0"            
                Margin="5,5"
                Orientation="Horizontal"                    
                Button.Click="Button_Click">
                
                <RadioButton
                    Content="Rajzolás"                
                    Margin="5,5"                
                    Tag="Ink"
                    IsChecked="True"/>
                <RadioButton
                    Content="Kiválasztás"
                    Margin="5,5"
                    Tag="Select"/>            
                <RadioButton
                    Content="Stroke törlése"
                    Tag="EraseByStroke"
                    Margin="5,5"/>
                <RadioButton
                    Content="Pont törlése"
                    Tag="EraseByPoint"
                    Margin="5,5"/>
                <Button
                    Content="Vászon törlése"
                    Tag="Clear"
                    Margin="5,5"/>
                
                <ComboBox       
                    Name="ComboBox_DrawingAttrWidth"
                    Margin="5,5"
                    Width="50"
                    SelectionChanged="ComboBox_DrawingAttrWidth_SelectionChanged">
                    <ComboBox.Items>
                        <ComboBoxItem Content="2"/>
                        <ComboBoxItem Content="4" IsSelected="True"/>
                        <ComboBoxItem Content="8"/>
                        <ComboBoxItem Content="12"/>
                        <ComboBoxItem Content="16"/>
                        <ComboBoxItem Content="20"/>
                        <ComboBoxItem Content="24"/>
                        <ComboBoxItem Content="28"/>
                        <ComboBoxItem Content="32"/>
                    </ComboBox.Items>
                </ComboBox>
                
                <Button
                    Name="Button_Analyze"
                    Content="Analizál"
                    Margin="5,5"
                    FontWeight="Bold"
                    Click="Button_Analyze_Click"/>
            </StackPanel>
    
            <!-- vászon -->
            <InkCanvas 
                Grid.Row="1"
                Name="ICanvas">
                <InkCanvas.DefaultDrawingAttributes>
                    <Ink:DrawingAttributes
                                xmlns:ink="system-windows-ink"
                                Color="Indigo"
                                Height="4" Width="4"/>
                </InkCanvas.DefaultDrawingAttributes>
            </InkCanvas>
    
            <!-- az eredmény megjelenítése -->
            <TextBlock 
                Grid.Row="2"
                Name="TextBlock_RecognizedText"
                Margin="5,5"
                Background="LightYellow"
                FontSize="32"/>
        </Grid>
    </Window>
    

    Megjegyzés: A StackPanelben látható egy Button.Click="Button_Click" rész. Nincs szükség minden radiobuttonnál egyesével manuálisan megadni a megfelelő eseménykezelőt, hála a RoutedEventeknek, ami egyedülálló a WPF-ben. Ezzel csak annyi a probléma, hogy a többi vezérlő esetében is - ha nem írjuk felül a Click eseményt - le fog futni. Ezt kezelni kell majd a codebehindban.

    Az InkCanvasnak beállítottam a színét, illetve a méretét (Ink:DrawingAttributes). Ebbe a kódba több érdekesség nincs is, úgyhogy itt van még a codebehind is hozzá:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using System.Windows.Ink;
    
    namespace WPFInkApi
    {
        /// <summary>
        /// Interakciós logika a WinMain-hez
        /// </summary>
        public partial class WinMain : Window
        {
            public WinMain()
            {
                InitializeComponent();
            }
    
            /// <summary>
            /// InkCanvas mód kiválasztása
            /// </summary>
            private void Button_Click(object sender, RoutedEventArgs e)
            {            
                Control control = e.Source as Control;
                if (control == null)
                    return;
    
                // megfelelő mód kiválasztása
                switch ((string)control.Tag)
                {
                    case "Ink":
                        ICanvas.EditingMode = InkCanvasEditingMode.Ink;
                        break;
    
                    case "Select":
                        ICanvas.EditingMode = InkCanvasEditingMode.Select;
                        break;
    
                    case "EraseByStroke":
                        ICanvas.EditingMode = InkCanvasEditingMode.EraseByStroke;
                        break;
    
                    case "EraseByPoint":
                        ICanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
                        break;
    
                    case "Clear":
                        ICanvas.Strokes.Clear();
                        break;
                }
            }
           
            /// <summary>
            /// Vonal vastagság
            /// </summary>
            private void ComboBox_DrawingAttrWidth_SelectionChanged(object sender,
                SelectionChangedEventArgs e)
            {
                // ez kell mert egyszer le fog futni
                // induláskor a ComboBoxItem IsSelected="True" beállítása miatt!
                if (ICanvas == null)
                    return;
    
                ComboBoxItem item = ComboBox_DrawingAttrWidth.SelectedValue as ComboBoxItem;
                // ha van kiválasztva valami...
                if (item != null)
                {
                    double size = Convert.ToDouble(item.Content);
    
                    ICanvas.DefaultDrawingAttributes.Width = size;
                    ICanvas.DefaultDrawingAttributes.Height = size;
                }
            }
    
            /// <summary>
            /// Analizálás
            /// </summary>  
            private void Button_Analyze_Click(object sender, RoutedEventArgs e)
            {
                // az AddStrokes() dob egy exceptiont
                // ha üres kollekciót adunk át (vagy null-t)
                if (ICanvas.Strokes.Count == 0)
                    return;
    
                // InkAnalyzer létrehozása
                InkAnalyzer Analyzer = new InkAnalyzer(this.Dispatcher);
                
                // az InkCanvas tartalmának átadása
                // egy nyelv azonosítóval (magyar nincs)
                Analyzer.AddStrokes(ICanvas.Strokes, 1033);
    
                // analizálás (ha sikerül, akkor a legjobb
                // eredményt adja vissza: GetRecognizedString())
                AnalysisStatus Status = Analyzer.Analyze();
                if (Status.Successful)
                    TextBlock_RecognizedText.Text = Analyzer.GetRecognizedString();
            }
        }
    }
    

    S az eredmény (remélem senki se gondolja ezt komolyan rólam :)):

    WPF InkCanvas

    Kész is volnánk, semmi több. Annyi gáz van vele, hogy a magyar ékezetes karaktereket így sajnos nem ismeri fel. Erre lehet használni a ch9 videóban is látott kis felülvonós trükköt. Sajnos ez még ugyanúgy, akárcsak a beszédfelismerés gyerekcipőben jár, s nemhogy csak gyerekcipőben, de szerintem megint mi magyarok leszünk az utolsók, akiknek lesz ilyenünk, mégha kész is lesz valaha (lsd. Vista beszédfelismerés). Hosszútávon viszont bízok benne, hogy kezdünk átlépni egy tényleg új, csodálatos korszakba, ahol az eddigi álmok valóra válnak!

    April 28

    WPF alkalmazások lokalizációja III.


    Befejezésképp, a teszteléssel kapcsolatban még egy dolog lemaradt:

    Ha lokalizálni szeretnénk alkalmazásainkat, akkor bizony elég körülményes lesz tesztelni azt:

    1. Átállítjuk mindig a nyelvét a Windowsnak (Vista Ultimate), feltéve ha használjuk a többnyelvű felületet (MUI).
    2. Lokalizált Windowson, ami nem Vista Ultimate meg egyenesen ez nem is lehetséges.

    Megoldás:

    Egyik megoldás, hogy átállítjuk az alkalmazás UI kultúráját az App.xaml.cs fájlban, pl. így:

    ...
    using
    System.Globalization; using System.Threading; namespace PowerOfWPF { public partial class App : Application { public App() { Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US"); } } }

    Ezzel csak az a gond így, hogy minden nyelv esetében meg kell változtassuk a kultúra azonosító paramétert és rebuildelni az egész projektet. Egy másik alternatíva, hogy készítünk egy kis egyszerű alkalmazást, amellyel könnyen tesztelhetjük a projektünket, mely elérhető a WPF SDK blogban itt, úgyhogy nincs értelme ezt tovább ragozni, itt van minden:

    http://blogs.msdn.com/wpfsdk/archive/2007/05/23/testing-applications-in-different-cultures.aspx

    April 27

    Egy kis WPF 3D demo

    Mindenkitől elnézést kérek, hogy mostanában nem írtam semmit, de sok dolgom volt, meg lesz is a közeljövőben. Mindenesetre megpróbálok valami kis érdekességet ide néha napján bedobni. Tervezem, hogy indítok egy Blend sorozatot is, mert arról még úgysincs nagyon magyar olvasmány (többek között design, arculat tervezéssel kapcsolatban is, bár nekem ez a terület még elég új, de a WPF óta egyre nagyobb jövőt látok benne).

    Ha emlékszünk még erre a kis demóra, amit a DirectShow bemutatása kapcsán írtam: videó textúra, ami nem volt más mint egy kocka, ami körbe-körbe forgott videó textúrával, akkor emlékezhetünk arra is, hogy nem éppen trivi ilyeneket alacsonyszintű DirectX, ill. OpenGL utasításokkal írogatni. Na jó, nem túl megeröltető, de most ettől sokkal dúrvábbat mutatok:

    C# kódot egy sort nem kell írni, ennyi az egész:

    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    
        mc:Ignorable="d"
        x:Name="Window" x:Class="_3DApp.Window1"    
        Background="Black" WindowStyle="None"
        WindowState="Maximized" ResizeMode="NoResize">
    
        <Viewport3D>
            
            <!-- kamera -->
            <Viewport3D.Camera>
                <PerspectiveCamera
                    FarPlaneDistance="20" 
                    LookDirection="0,-0.65,-1" 
                    UpDirection="0,1,0" 
                    NearPlaneDistance="1" 
                    Position="0,2,3"
                    FieldOfView="40">
                    <PerspectiveCamera.Transform>
                        <Transform3DGroup>
                            <TranslateTransform3D
                                OffsetX="0" OffsetY="0" OffsetZ="0"/>
                            <ScaleTransform3D
                                ScaleX="1" ScaleY="1" ScaleZ="1"/>
                            <RotateTransform3D d:EulerAngles="0,0,0"/>
                            <TranslateTransform3D
                                OffsetX="0" OffsetY="0" OffsetZ="0"/>
                            <TranslateTransform3D
                                OffsetX="0" OffsetY="0" OffsetZ="0"/>
                        </Transform3DGroup>
                    </PerspectiveCamera.Transform>
                </PerspectiveCamera>
            </Viewport3D.Camera>
            
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <Model3DGroup>                                        
                        <GeometryModel3D>                        
                            <!-- model -->
                            <GeometryModel3D.Geometry>
                                <MeshGeometry3D
                                    TriangleIndices=
                                        "0,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 "
                                    Normals=
                                        "0,0,-1 0,0,-1 0,0,
                                        -1 0,0,-1 0,0,-1 0,0,
                                        -1 0,0,1 0,0,1 0,0,1 0,
                                        0,1 0,0,1 0,0,1 0,-1,0
                                        0,-1,0 0,-1,0 0,-1,0 0,
                                        -1,0 0,-1,0 1,0,0 1,0,0 1,
                                        0,0 1,0,0 1,0,0 1,0,0 0,1,
                                        0 0,1,0 0,1,0 0,1,0 0,1,
                                        0 0,1,0 -1,0,0 -1,0,0 -1,
                                        0,0 -1,0,0 -1,0,0 -1,0,0"
                                    TextureCoordinates=
                                        "1,1 1,0 0,0 0,0 0,1 1,
                                        1 0,1 1,1 1,0 1,0 0,0 0,
                                        1 0,1 1,1 1,0 1,0 0,0 0,
                                        1 1,1 1,0 0,0 0,0 0,1
                                        1,1 1,0 0,0 0,1 0,1 1,
                                        1 1,0 0,0 0,1 1,1 1,1 1,0 0,0"
                                    Positions=
                                        "-0.5,-0.5,-0.5 -0.5,0.5,
                                        -0.5 0.5,0.5,-0.5 0.5,0.5,
                                        -0.5 0.5,-0.5,-0.5 -0.5,-0.5,
                                        -0.5 -0.5,-0.5,0.5 0.5,-0.5,
                                        0.5 0.5,0.5,0.5 0.5,0.5,0.5 -0.5,
                                        0.5,0.5 -0.5,-0.5,0.5 -0.5,-0.5,-0.5
                                        0.5,-0.5,-0.5 0.5,-0.5,0.5 0.5,-0.5,
                                        0.5 -0.5,-0.5,0.5 -0.5,-0.5,-0.5 0.5,
                                        -0.5,-0.5 0.5,0.5,-0.5 0.5,0.5,0.5 0.5,
                                        0.5,0.5 0.5,-0.5,0.5 0.5,-0.5,-0.5 0.5,
                                        0.5,-0.5 -0.5,0.5,-0.5 -0.5,0.5,
                                        0.5 -0.5,0.5,0.5 0.5,0.5,0.5 0.5,
                                        0.5,-0.5 -0.5,0.5,-0.5 -0.5,-0.5,
                                        -0.5 -0.5,-0.5,0.5 -0.5,-0.5,0.5 -0.5,
                                        0.5,0.5 -0.5,0.5,-0.5"/>
                            </GeometryModel3D.Geometry>
                            
                            <!-- material -->
                            <GeometryModel3D.Material>
                                <DiffuseMaterial>
                                    <DiffuseMaterial.Brush>
                                        <VisualBrush>
                                            <VisualBrush.Visual>
                                                <MediaElement
                                                    LoadedBehavior="Play"
                                                    UnloadedBehavior="Close"
                                                    Source="C:\Temp\Video.wmv"/>
                                            </VisualBrush.Visual>
                                        </VisualBrush>
                                    </DiffuseMaterial.Brush>
                                </DiffuseMaterial>
                            </GeometryModel3D.Material>
                            
                            <!-- transzformációk -->
                            <GeometryModel3D.Transform>
                                <RotateTransform3D>
                                    <RotateTransform3D.Rotation>
                                        <AxisAngleRotation3D 
                                            x:Name="Rotation3D"
                                            Angle="45"
                                            Axis="0 1 0"/>
                                    </RotateTransform3D.Rotation>
                                </RotateTransform3D>
                            </GeometryModel3D.Transform>
                        </GeometryModel3D>
                                                               
                        <!-- fény -->
                        <AmbientLight Color="White"/>
                    </Model3DGroup>
                </ModelVisual3D.Content>
            </ModelVisual3D>
    
            <!-- triggerek -->
            <Viewport3D.Triggers>
                <EventTrigger RoutedEvent="Viewport3D.Loaded">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation
                                    From="0" To="360" Duration="0:0:15" 
                                    Storyboard.TargetName="Rotation3D"
                                    Storyboard.TargetProperty="Angle"
                                    RepeatBehavior="Forever"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Viewport3D.Triggers>
    
        </Viewport3D>
    
    </Window>

    Mindössze egy Viewport3D objektumra van szükség, ami egy renderelési felületet ad a 3D-s vizuális tartalomhoz. Kell egy kamera (PerspectiveCamera), egy mesh (MeshGeometry3D), ami definiálja a modellt. A material egy DiffuseMaterial lesz, aminek egy MediaElement képét fogjuk átadni, mégpedig egy visual brush segítségével, akárcsak a tükrözéses példáknál. Itt látszik, hogy a visual brushnak a design tervezésben elég sok szerepe van/lehet a jövőben. Aztán definiálunk egy forgatási transzformációt, aminek a szögét folyamatosan változtatni fogjuk. Ezt látjuk legalul a Viewport3D triggerek között, ami a Viewport3D.Loaded esemény lefutásakor elindul, természetesen a DoubleAnimation RepeateBehavior tulajdonságát Foreverre állítjuk. Idáig ok minden, most már csak valami fényt kell rátenni, mely meglepően és mulatságosan egyszerű: <AmbientLight Color="White"/>. El lehet vele kezdeni szórakozni egy kicsit a Blendben:

    Blend

    Én úgy látom, a WPF kisebb "mutatványok", látványosságok gyártására kiválóan alkalmas azok számára is, akiktől ez a terület teljesen idegen. Hála Istennek! Egy pillantást szerintem megér mindenkinek.

    Jó szórakozást hozzá!

    April 05

    WPF alkalmazások lokalizációja II.


    Még azt hozzátenném az előzőekhez, hogy ha külön assemblybe fordítjuk az eredeti nyelvi erőforrásokat is, akkor a designer megborul. Itt nem arról van szó, hogy a Cider hibás, a Blend designer is megborul azon egyszerű oknál fogva, hogy innentől kezdve a Resources.en-US.resx-ben keresi az erőforrásokat. A megoldás egyszerű: másolatot kell készíteni az eredeti Resources.resx-ről Resources.en-US.resx néven (a *.designer.cs fájlok nem kellenek):

    Files

    Ja igen, továbbá a Resources.resx-et kiválasztva a Build Action tulajdonságot a properties ablakban állítsuk át Embedded Resource-ról None-ra, különben a main assemblybe is beágyazódnak az angol nyelvű erőforrások teljesen feleslegesen:

    Build Action

    Ezzel a módszerrel lehet dolgozni, ámde egy idő után kényelmetlenné válik. Minden lokalizálandó tulajdonságot kötözgetni eléggé cicókás. A következő módszer talán segít mindezen.

     

    Lokalizáció II.

    • Az egész WPF lokalizáció abból indul ki, hogy vannak fejlesztők, designerek, illetve alkalmazás lokalizálók. Az elv egyszerű, a fejlesztő és a designer elkészíti az UI-t a saját nyelvén, majd a végén jön a lokalizáló, aki lefordít mindent amit kell. Nézzünk meg egy példát:

      Először is készítsünk egy kis egyszerű WPF alkalmazást:

      Step 1
       
      <Windowx:Class="PowerOfWPF.WinMain"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         Title="Power of WPF"
         SizeToContent="WidthAndHeight"
         WindowStartupLocation="CenterScreen">
          <Grid>
             <!-- rows -->
             <Grid.RowDefinitions>
                  <RowDefinitionHeight="Auto"/>
                  <RowDefinitionHeight="*"/>
                  <RowDefinitionHeight="Auto"/>
              </Grid.RowDefinitions>
              
             <!-- main menu -->
             <MenuGrid.Row="0">
                  <MenuItemHeader="_File"/>
                  <MenuItemHeader="_Edit"/>
                  <MenuItemHeader="_View"/>
                  <MenuItemHeader="_Tools"/>            
                  <MenuItemHeader="_Help"/>
              </Menu>
              
             <!-- listbox -->
             <ListBoxGrid.Row="1">
                  <ListBox.Items>
                      <ListBoxItemContent="One"/>
                      <ListBoxItemContent="Two"/>
                      <ListBoxItemContent="Three"/>
                      <ListBoxItemContent="Four"/>
                      <ListBoxItemContent="Five"/>
                  </ListBox.Items>
              </ListBox>
              
             <!-- author -->
             <TextBlock
                 Grid.Row="2"
                 FontSize="12"
                 Background="Gold"            
                 Text="Copyright 2008 Janka János - All rights reserved!"/>
          </Grid>
      </Window>

      Láthatjuk az ablak SizeToContent tulajdonságát beállítottam, hogy automatikusan a tartalomhoz méreteződjön az ablak, illetve a Grid-et 3 részre osztottam, az első sorba kerül a menü (Menu Grid.Row="0"), a másodikba egy ListBox, aminek vannak tételei, amik jelen esetben egyszerű string-ek, illetve a 3. sorba egy TextBlock szerzői jogi információkkal.

    • A .csproj fájlba elhelyezzük a szokásos UICulture tagot a semleges nyelvi erőforrások meghatározása végett:

      <?xml version="1.0" encoding="utf-8"?>
      <Project
      ToolsVersion="3.5"
      DefaultTargets="Build"
      xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <UICulture>en-US</UICulture>
      Illetve kommentezzük ki az AssemblyInfo.cs fájlba az ide vonatkozó részt, hogy tesztelhető legyen a projekt, ha esetleg a régió beállítások nem egyeznek meg a projekt kultúrával:
      [assembly: NeutralResourcesLanguage("en-US",
      UltimateResourceFallbackLocation.Satellite)]


    • Ezután vagy manuálisan adunk mindennek, amit le szeretnénk fordítani egy x:Uid egyedi azonosítót, vagy auto generálunk mindennek egyet az msbuild segítségével. Ez utóbbi nagyon egyszerű:

      Generál mindennek egy ID-et:
      msbuild /t:updateuid PowerOfWPF.csproj

      Ellenőrzi, hogy nincs két egyforma ID:
      msbuild /t:checkuid PowerOfWPF.csproj

      Az eredmény:

      <Window x:Uid="Window_1" x:Class="PowerOfWPF.WinMain"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          Title="Power of WPF"
          SizeToContent="WidthAndHeight"
          WindowStartupLocation="CenterScreen">
          <Grid x:Uid="Grid_1">
              <!-- rows -->
              <Grid.RowDefinitions>
                  <RowDefinition x:Uid="RowDefinition_1" Height="Auto"/>
                  <RowDefinition x:Uid="RowDefinition_2" Height="*"/>
                  <RowDefinition x:Uid="RowDefinition_3" Height="Auto"/>            
              </Grid.RowDefinitions>
              
              <!-- main menu -->
              <Menu x:Uid="Menu_1" Grid.Row="0">
                  <MenuItem x:Uid="MenuItem_1" Header="_File"/>
                  <MenuItem x:Uid="MenuItem_2" Header="_Edit"/>
                  <MenuItem x:Uid="MenuItem_3" Header="_View"/>
                  <MenuItem x:Uid="MenuItem_4" Header="_Tools"/>            
                  <MenuItem x:Uid="MenuItem_5" Header="_Help"/>
              </Menu>
              
              <!-- listbox -->
              <ListBox x:Uid="ListBox_1" Grid.Row="1">
                  <ListBox.Items>
                      <ListBoxItem x:Uid="ListBoxItem_1" Content="One"/>
                      <ListBoxItem x:Uid="ListBoxItem_2" Content="Two"/>
                      <ListBoxItem x:Uid="ListBoxItem_3" Content="Three"/>
                      <ListBoxItem x:Uid="ListBoxItem_4" Content="Four"/>
                      <ListBoxItem x:Uid="ListBoxItem_5" Content="Five"/>
                  </ListBox.Items>
              </ListBox>
              
              <!-- author -->
              <TextBlock                        
                  x:Uid="TextBlock_1" Grid.Row="2"
      FontSize="12" Background="Gold"
      Text="Copyright 2008 Janka János - All rights reserved!"/> </Grid> </Window>
    • Következő lépésben felmérjük, hogy mik azok a dolgok, amiket nem szeretnénk, ha lokalizálnának. Erre találták ki a lokalizációs attribútumokat és kommenteket. Pl. ha nem szeretnénk, hogy lokalizálják a TextBlock tartalmát a szerzői jogi információkkal, illetve a betűtípust se változtassák meg, akkor a TextBlock-ot a következőképp módosíthatjuk:
      <!-- author -->
      <TextBlock                        
          x:Uid="TextBlock_1" Grid.Row="2" FontSize="12" Background="Gold"            
          Localization.Attributes="$Content (Unmodifiable Readable Text)
                                   FontFamily (Unmodifiable Readable)"
          Localization.Comments="$Content (Trademark)
                                 FontSize (Trademark font size)"
          Text="Copyright 2008 Janka János - All rights reserved!"/>
      Beállíthatjuk a .csproj fájlba azt is, hogy egy külön .loc fájlba kerüljönek bele a projektben található lokalizációval kapcsolatos attribútum és megjegyzési információk:
      <?xml version="1.0" encoding="utf-8"?>
      <Project
      ToolsVersion="3.5"
      DefaultTargets="Build"
      xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <UICulture>en-US</UICulture> <LocalizationDirectivesToLocFile>All</LocalizationDirectivesToLocFile>

      Pl. egy ilyen fájl tartalma (PowerOfWPF.loc):

      <LocalizableAssembly>
          <LocalizableFile Name="WinMain">
              <LocalizationDirectives
      Uid="TextBlock_1"
      Attributes="$Content (Unmodifiable Readable Text)
      FontFamily (Unmodifiable Readable)"
      Comments="$Content (Trademark)
      FontSize (Trademark font size)" /> </
      LocalizableFile> </LocalizableAssembly>

      Ez tulajdonképp információval szolgál azoknak az emberkéknek, akik majd fordítgatják a cuccot kínaira, s korántsem az a célja, hogy ne lehessen lokalizálni valamit. Ez utóbbi cél eléréséhez elég az uid-et elhagyni. További részletek itt: Localization Attributes and Comments

    • Most buildelünk egyet és létrejön a debug mappában a semleges nyelvű erőforrás assembly:
      ...\PowerOfWPF\bin\Debug\en-US\PowerOfWPF.resources.dll

    • Ezután töltsük le a LocBaml alkalmazást (ez egy önkicsomagolós kis ZIP fájl), fordítsuk le és másoljuk az alkalmazásunk debug mappájába, ahol az exe is van. A fordítás gyorsítása végett elég ennyit beírni: msbuild locbaml.csproj

    • Generáljunk a LocBaml segítségével egy .csv fájlt a következő módon:
      LocBaml.exe /parse en-US/PowerOfWPF.resources.dll /out:PowerOfWPF.csv

    • Készítsünk egy angol erőforrás assemblyt először (ehhez Windows Vista-n rendszergazda joggal kell futtatni a LocBaml.exe-t, úgyhogy előtte még a tulajdonságlapon ezt állítsuk be):

      LocBaml.exe /generate en-US\PowerOfWPF.resources.dll /trans:PowerOfWPF.csv /out:C:\ /cul:en-US

      Ez most így generált egy PowerOfWPF.resources.dll nevű assemblyt a .csv fájlból, ami meg lett jelölve az en-US kultúrával. Ezzel cseréljük ki/írjuk felül az eredeti (régi) en-US mappában található assemblyt.

    • Lemásoljuk a .csv fájlt és mondjuk adunk neki egy értelmes nevet, ami a magyar (lokalizált) erőforrásokat fogja tartalmazni, pl. PowerOfWPF.hu.csv. Ezt követően nyissuk meg, lehetőleg ne Excellel, bármennyire is az SDK így írja, mert telerakja szeméttel és utána nem is működik a felesleges vesszőktől a program. Egy egyszerű jegyzettömb is megteszi és fordítsuk le a szövegeket.

    • Ezután generáljuk újra a magyar nyelvi erőforrássokat (satellit assembly):
      LocBaml.exe /generate en-US\PowerOfWPF.resources.dll /trans:PowerOfWPF.hu.csv /out:C:\ /cul:hu

    • A létrehozott assemblyt pedig helyezzük a ..\debug\hu mappába (vagy inkább egyből ott hozzuk létre), s ha mindent jól csináltunk, akkor íme az eredmény:

      image

    Na most azt hozzáteszem, hogy ez korántsem egyszerű eset. Nem tudom ki volt az értelmi szerző, de ezen még van mit csiszolni. Kellene valami designtime támogatás ehhez, mert ez így még mindig kínlódás. Persze működik, szó se róla, csak ezt a sorozatot végigjátszani nem éppen a kedvenc időtöltéseim közé tartozik :).

    Az előző példát lehet csavarni még egy picit, mondjuk sablonokkal, tömbökkel:

    image 

    <Window x:Uid="Window_1" x:Class="PowerOfWPF.WinMain"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:PowerOfWPF"
        Title="Power of WPF"
        SizeToContent="WidthAndHeight"
        WindowStartupLocation="CenterScreen">
        
        <Grid x:Uid="Grid_1">
            <!-- rows -->
            <Grid.RowDefinitions>
                <RowDefinition x:Uid="RowDefinition_1" Height="Auto"/>
                <RowDefinition x:Uid="RowDefinition_2" Height="*"/>
                <RowDefinition x:Uid="RowDefinition_3" Height="Auto"/>            
            </Grid.RowDefinitions>
            
            <!-- main menu -->
            <Menu x:Uid="Menu_1" Grid.Row="0">
                <MenuItem x:Uid="MenuItem_1" Header="_File"/>
                <MenuItem x:Uid="MenuItem_2" Header="_Edit"/>
                <MenuItem x:Uid="MenuItem_3" Header="_View"/>
                <MenuItem x:Uid="MenuItem_4" Header="_Tools"/>            
                <MenuItem x:Uid="MenuItem_5" Header="_Help"/>
            </Menu>
            
            <!-- listbox -->
            <ListBox x:Uid="ListBox_1" Grid.Row="1">
                <!-- item template -->
                <ListBox.ItemTemplate>
                    <DataTemplate
                        x:Uid="DataTemplate_1"
                        DataType="{x:Type local:Number}">
                        <StackPanel x:Uid="StackPanel_1"
                                    Orientation="Horizontal" Margin="2,2">
                            <Button x:Uid="Button_1"                                
                                    HorizontalAlignment="Right"
                                    Content="{Binding Path=IntValue}"/>
                            <TextBox x:Uid="TextBox_1"                                 
                                     BorderThickness="0"
                                     Text="{Binding Path=StringValue}"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
    
                <!-- items -->
                <ListBox.ItemsSource>
                    <x:Array x:Uid="x:Array_1" Type="{x:Type local:Number}">
                        <local:Number x:Uid="local:Number_1"
                                      IntValue="1" StringValue="One"/>
                        <local:Number x:Uid="local:Number_2"
                                      IntValue="2" StringValue="Two"/>
                        <local:Number x:Uid="local:Number_3"
                                      IntValue="3" StringValue="Three"/>
                        <local:Number x:Uid="local:Number_4"
                                      IntValue="4" StringValue="Four"/>
                        <local:Number x:Uid="local:Number_5"
                                      IntValue="5" StringValue="Five"/>
                    </x:Array>                
                </ListBox.ItemsSource>           
            </ListBox>
            
            <!-- author -->
            <TextBlock                        
                Grid.Row="2" FontSize="12" Background="Gold"
                Text="Copyright 2008 Janka János - All rights reserved!"/>
        </Grid>
    </Window>    

    Itt mindössze annyi változás állt be, hogy csináltam egy Number osztályt két tulajdonsággal (IntValue, StringValue), illetve egy DataTemplate-t ehhez. A listbox itemeket töröltem, az itemsource-nak beállítottam a number tömböt. Ezután nyomtam egy frissítést: msbuild /t:updateuid PowerOfWPF.csproj, majd végül a TextBlock-ról levettem az uid-et, hogy ne lehessen lokalizálni. Ezután minden úgy megy tovább, mint ahogy fentebb is írtam:

    image

    Jó szórakozást hozzá mindenkinek!
    Remélem azért így már valamivel csak könnyebb lesz másoknak is.