János's profileJános JankaPhotosBlogListsMore ![]() | Help |
|
April 29 Kézírás felismerés a WPF-ben
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 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 :)): 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.
Ha lokalizálni szeretnénk alkalmazásainkat, akkor bizony elég körülményes lesz tesztelni azt:
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: ... 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 demoMindenkitő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: É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.
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: 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.
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: <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: Jó szórakozást hozzá mindenkinek! |
|
|