Banner

2011. november 11., péntek

Take over control...


Kövesd a MoDev-etGoogle+ -on


Habár a WP7 rengeteg beépített controllal rendelkezik, az egyedi alkalmazások fejlesztésénél jogosan merül
fel az igény egyedi kontrollok készítésére. Ezért gondoltam, hogy az első példakódnál egy ilyen megvalósítását mutatom be.


Cél: Szeretnénk egy UserControl-t, melynek segítségével egy Lista elrendezésű galériát valósíthatunk meg.
Ha nyomva tartjuk az ujjunkat egy képen akkor felnagyítódik.


Ez lesz az eredmény:


  1. Új projekt

    Első lépésben Visual Studio-ban hozzunk létre egy új WP7 projektet. A listában az első Windows
    Phone Application-t válasszuk ki. A kérdésre, hogy milyen verziójú projectet szeretnénk válasszuk az
    OS 7.1 (Mango) opciót.



    Ezután rögtön menjünk is át Expression Blend-be hogy létrehozzuk a kontrollunk elrendezését.
    Ezt úgy tehetjük meg, hogy a project főoldalát reprezentáló MainPage.xaml fájlon jobbgomb után
    kiválasztjuk az Open in Expression Blend menüpontot....
  2. Expression Blend, új user control

    Itt ez a látvány fogad minket:


    A kezdőoldalunkat látjuk, melyről gyorsan távolítsuk el a Visual Studio által elhelyezett title és
    contetn panelt. erre azért van szükség, hogy a leendő listánk az egész képernyőt birtokolja.

    Most adjunk hozzá egy új User Controlt az projecthez. Ezt a File/New Item menüpont alatt tehetjük
     meg. Itt válasszuk a Usercontrolt, majd adjunk a StretchingImageControl nevet neki.
    Ezután letrejön az új control-unk, egy külön XAML fájban, és legenerálódik hozzá a C# objektum is




    Állítsuk a UserControl méretét auto-ról 480x100-asra, ez ahhoz kell hogy a későbbi skálázás során
    hozzá alklamazkodva nyúljanak a benne lévő elemek.
    Adjunk hozzá egy négyzetet, malynek méretezése automatikus legyen, illetve az alingmentjei mind
    horizontálisan, mind vertikálisan Strech-re legyenek állítva.
    Most adjuk, hozzá a kép címét megjelenítő TextBlock-ot amit a négyzet aljában helyezünk el.

    Ez XAML-ben így fog kinézni:

    <UserControl
        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:Class="ExpandingGallery.StretchingImageControl"
        d:DesignWidth="480" Width="480" Height="100">
    
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <Rectangle Stroke="Black">
                <Rectangle.Fill>
                    <ImageBrush Stretch="None"/>
                </Rectangle.Fill>
            </Rectangle>
            <TextBlock Margin="4,0,8,4" TextWrapping="Wrap" Text="TextBlock"FontSize="45.333" 
    
    VerticalAlignment="Bottom" d:LayoutOverrides="Width, HorizontalMargin"
    HorizontalAlignment="Left"/></UserControl>

    Expression Blendben pedig így:

    Fontos, hogy a Rectangle/ImageBrush Stretch property-je None-ra legyen állítva, ennek köszönhetően
    majd csak egy szeletet látunk a képből.
    Ez így nagyon Metrós lesz :)

      
  3. Control animációi:

    A Control megjelenítési állapotait VisualState-ekben tudjuk megadni. Az átmeneteket ezen State-ek
    között fogjuk definiálni, és a képernyőérintés eseményeihez fogjuk hozzárendelni a későbbiekben.

    Elsőnek jöjjön a XAML aztán a magyarázat:

    <UserControl
        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" x:Name="userControl"
        mc:Ignorable="d"
        x:Class="ExpandingGallery.StretchingImageControl"
        d:DesignWidth="480" Width="480" Height="100">
    
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="CommonStates">
                    <VisualStateGroup.Transitions>
                        <VisualTransition From="Normal" GeneratedDuration="0" To="Pressed">
                            <VisualTransition.GeneratedEasingFunction>
                                <BackEase EasingMode="EaseIn"/>
                            </VisualTransition.GeneratedEasingFunction>
                            <Storyboard>
                                <DoubleAnimationUsingKeyFrames 
                               Storyboard.TargetProperty="(FrameworkElement.Height)"
                               Storyboard.TargetName="this">
    <EasingDoubleKeyFrame KeyTime="0" Value="100"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="800"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualTransition> <VisualTransition From="Pressed" GeneratedDuration="0" To="Normal"> <VisualTransition.GeneratedEasingFunction> <BackEase EasingMode="EaseIn"/> </VisualTransition.GeneratedEasingFunction> <Storyboard> <DoubleAnimationUsingKeyFrames
    Storyboard.TargetProperty="(FrameworkElement.Height)"
    Storyboard.TargetName="this">
    <EasingDoubleKeyFrame KeyTime="0" Value="800"/> <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="100"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualTransition> <VisualTransition GeneratedDuration="0"/> </VisualStateGroup.Transitions> <VisualState x:Name="Normal"> <Storyboard/> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <DoubleAnimation Duration="0" To="800" Storyboard.TargetProperty="(FrameworkElement.Height)"
    Storyboard.TargetName="this" d:IsOptimized="True"/>
    </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <Rectangle Stroke="Black"> <Rectangle.Fill> <ImageBrush Stretch="None"/> </Rectangle.Fill> </Rectangle> <TextBlock Height="67" Margin="0,33,8,0" TextWrapping="Wrap"
    Text="TextBlock" VerticalAlignment="Top" FontSize="56" d:LayoutOverrides="VerticalAlignment"/>
    </Grid> </UserControl>

    Létrehoztunk egy VisualStateManager-t amiben egy Groupot vettünk fel, itt lettek elhelyezve a Pressed
    és a Normal state közötti animációk (átmenetek). Ez ExpressionBlend GUI-ban nagyon könnyen
    összekattintgatható. Kézzel XAML-ben leírni... nem tanácsos...
  4. Eseménykezelők, és az adatkötés előkészítése a controlunkon...

    Térjünk vissza VisualStudioba és nyissuk meg a StretchingImageControl.xaml.cs -t. Itt hozzá fogunk
    adni új DependencyProperty-ket a controlunk-hoz. Ennek köszönhetően XAML-ben tudunk ezen
    property-kkel dolgozni vagy akár adatkötni.

    Emellett a konstruktorban beállítjuk a kiindulási VisualState-et, ezutén pedig létrehozzuk az
    eseménykezelőket amik az érintés esetén fognak reagálni.


    namespace ExpandingGallery
    {
        public partial class StretchingImageControl : UserControl
        {
            //Creating dep props, for the title and the source path
            public static readonly DependencyProperty TitleProperty =
            DependencyProperty.Register("ImageTitle",
            typeof(string),
            typeof(StretchingImageControl),
            new PropertyMetadata(null));
    
            public static readonly DependencyProperty ImageSourceProperty =
            DependencyProperty.Register("ViewImage",
            typeof(string),
            typeof(StretchingImageControl),
            new PropertyMetadata(null));
    
            public StretchingImageControl()
            {
                // Required to initialize variables
                InitializeComponent();
                VisualStateManager.GoToState(this, "Normal", true);
            }
    
            public string Title
            {
                get { return (string)GetValue(TitleProperty); }
                set { SetValue(TitleProperty, value); }
            }
    
            public string ImageSource
            {
                get { return (string)GetValue(ImageSourceProperty); }
                set { SetValue(ImageSourceProperty, value); }
            }
    
            private void this_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                VisualStateManager.GoToState(this, "Pressed", true);
            }
    
            private void this_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
            {
                VisualStateManager.GoToState(this, "Normal", true);
            }
    
            private void this_MouseLeave(object sender, MouseEventArgs e)
            {
                VisualStateManager.GoToState(this, "Normal", true);
            }
        }
    }
    

    Ahhoz hogy meghívódjanak az eseménykezelők, természetesen a XAML-ben is fel kell őket tüntetni.
    Ezt megtesszük a UserControl nyitó tag-ben:

    <UserControl
        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:Class="ExpandingGallery.StretchingImageControl"
        d:DesignWidth="480" Width="480" Height="100" 
        x:Name="this" MouseLeave="this_MouseLeave" MouseLeftButtonUp="this_MouseLeftButtonUp" 
    
    MouseLeftButtonDown="this_MouseLeftButtonDown">

    Ezután a Rectangle és a Textbox megfelelő tulajdonságait hozzá kell kötnünk a Control osztály
    propertieihez.


    ...
    <Rectangle Stroke="Black">
                <Rectangle.Fill>
                    <ImageBrush Stretch="None" ImageSource="{Binding ViewImage, ElementName=this}"/>
                </Rectangle.Fill>
            </Rectangle>
            <TextBlock Margin="4,0,8,4" TextWrapping="Wrap" Text="{Binding ImageTitle, 
    
    ElementName=this}"
    FontSize="45.333" VerticalAlignment="Bottom" d:LayoutOverrides="Width, HorizontalMargin" HorizontalAlignment="Left"/> ...


  5. MainPage kialakítása, Lista hozzákötése saját adatforráshoz.

    Saját adatforrás - A listánkat egy kollekcióhoz fogjuk kötni, mely két, a mi controlunkhoz illeszkedő
    property-vel rendelkezik. Ehhez először létrehozunk egy osztályt mely az elemhez tartozó adatokat
    tárolja.

     public class GalleryImages
        {
            public string image { get; set; }
            public string ButtonText { get; set; }
        }
    

    Adjunk egy ListBox-ot a MainPage-ünkhöz, mely töltse ki a teljes képernyőt.
    A listBox-hoz adjunk hozzá egy DataTemplate-et mely a kollekció eleminek megjelenítését írja le.
    Itt az oldal elején a névtérhez hozzáadott saját control-unkat illesszük be, és a Title illetve az
    ImageSource property-nek adjuk meg a megfelelő binding értékeket...


    <phone:PhoneApplicationPage 
       .....
        xmlns:local="clr-namespace:ExpandingGallery"
       .....
    
    
        <!--LayoutRoot is the root grid where all page content is placed-->
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!--TitlePanel contains the name of the application and page title-->
    
            <!--ContentPanel - place additional content here-->
            <Grid x:Name="ContentPanel" Grid.RowSpan="2">
                <ListBox Name="StretchingGallery" Width="480">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <local:StretchingImageControl VerticalAlignment="Top" 
    
    Title="{Binding ButtonText}" Height="100" Width="480"
      ImageSource="{Binding image}"/> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Grid> </phone:PhoneApplicationPage>

    Menjünk át a MainPage.xaml.cs-be és hozzuk létre a kollekciót, majd töltsük fel pár elemmel.
    Ezek után kössük rá a ListBox-unk DataSource-ára (A képek amikre hivatkozok, benne vannak a
    letölthető Solution-ben)

    namespace ExpandingGallery
    {
        public partial class MainPage : PhoneApplicationPage
        {
            ObservableCollection<GalleryImages> ButtonList;
            public MainPage()
            {
                InitializeComponent();
    
                ButtonList = new ObservableCollection<GalleryImages>();
                ButtonList.Add(new GalleryImages { image = "1.jpg", ButtonText = "Excitement" });
                ButtonList.Add(new GalleryImages { image = "2.jpg", ButtonText = "Perfection" });
                ButtonList.Add(new GalleryImages { image = "3.jpg", ButtonText = "Elegance" });
                ButtonList.Add(new GalleryImages { image = "4.jpg", ButtonText = "Randomness" });
                StretchingGallery.ItemsSource = ButtonList;
            }
        }
    }
    

    Ezzel el is készültünk.....

    A kész Solution innen elérhető: