Archive

Archive for octobre 2009

[SL3] : 3D projection, Behaviors, PixelShaders

Petit post pour vous faire découvrir quelques nouveautés de la version 3 de Silverlight. 

La dernière version de Silverlight propose une mine de petits contrôles et outils de programmation qui permettent de faciliter la vie du développeur ! 

Effects

DropShadowEffect

Applique une ombre aux contrôles

DropShadowEffect

<Border BorderBrush="#FF90A4FE"
			BorderThickness="4"
			Background="White">
		<Border.Effect>
			<BlurEffect />
		</Border.Effect>
		<TextBlock TextWrapping="Wrap"
				   Text="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed..."
				   Margin="2" />
	</Border>

BlurEffect

Applique un effet de blur à vos contrôles

BlurEffect

<Border BorderBrush="#FF90A4FE"
			BorderThickness="4"
			Background="White">
		<Border.Effect>
			<DropShadowEffect Opacity="0.6" />
		</Border.Effect>
		<TextBlock TextWrapping="Wrap"
				   Text="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed..."
				   Margin="2" />
	</Border>

3D Projection

Vous permet d’effectuer des rotation à vos contrôles

PlaneProjection

<Border BorderBrush="#FF90A4FE"
			BorderThickness="4"
			Background="White">
		<Border.Projection>
			<PlaneProjection RotationX="-39" RotationY="-21"/>
		</Border.Projection>
		<TextBlock TextWrapping="Wrap"
				   Text="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed..."
				   Margin="2" />
	</Border>

Behaviors

DragBehavior

Ce behavior vous permet de déplacer votre controle sans aucune ligne de code !

<UserControl
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
	xmlns:il="clr-namespace:Microsoft.Expression.Interactivity.Layout;assembly=Microsoft.Expression.Interactions"
	x:Class="SilverlightApplication7.MainPage"
	>

	<Grid x:Name="LayoutRoot" Background="#FFDFDFDF">
		<Border Width="300" BorderBrush="#FF90A4FE" BorderThickness="4" Background="White" Height="110">
			<Border.Effect>
				<DropShadowEffect Opacity="0.6"/>
			</Border.Effect>
			<i:Interaction.Behaviors>
				<il:MouseDragElementBehavior/>
			</i:Interaction.Behaviors>
			<TextBlock TextWrapping="Wrap" Text="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed..." Margin="2"/>
		</Border>
	</Grid>
</UserControl>

ControlStoryboardAction

StoryBoardActionBehavior

<UserControl
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
	xmlns:il="clr-namespace:Microsoft.Expression.Interactivity.Layout;assembly=Microsoft.Expression.Interactions"
	xmlns:im="clr-namespace:Microsoft.Expression.Interactivity.Media;assembly=Microsoft.Expression.Interactions"
	x:Class="SilverlightApplication7.MainPage"
	>

	<UserControl.Resources>
		<Storyboard x:Name="StoryboardTest">
			<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)">
				<EasingDoubleKeyFrame KeyTime="00:00:01" Value="360">
					<EasingDoubleKeyFrame.EasingFunction>
						<PowerEase EasingMode="EaseInOut"/>
					</EasingDoubleKeyFrame.EasingFunction>
				</EasingDoubleKeyFrame>
				<EasingDoubleKeyFrame KeyTime="00:00:02" Value="0">
					<EasingDoubleKeyFrame.EasingFunction>
						<BackEase EasingMode="EaseInOut"/>
					</EasingDoubleKeyFrame.EasingFunction>
				</EasingDoubleKeyFrame>
			</DoubleAnimationUsingKeyFrames>
		</Storyboard>
	</UserControl.Resources>

	<Grid x:Name="LayoutRoot" Background="#FFDFDFDF">
		<Border x:Name="border" Width="300" BorderBrush="#FF90A4FE" BorderThickness="4" Background="White" Height="110">
			<Border.Effect>
				<DropShadowEffect Opacity="0.6"/>
			</Border.Effect>
			<i:Interaction.Behaviors>
				<il:MouseDragElementBehavior/>
			</i:Interaction.Behaviors>
			<Border.Projection>
				<PlaneProjection/>
			</Border.Projection>
			<TextBlock TextWrapping="Wrap" Text="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed..." Margin="2"/>
		</Border>
		<Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Content="Button" Margin="10,10,0,0">
			<i:Interaction.Triggers>
				<i:EventTrigger EventName="Click">
					<im:ControlStoryboardAction Storyboard="{StaticResource StoryboardTest}"/>
				</i:EventTrigger>
			</i:Interaction.Triggers>
		</Button>
	</Grid>
</UserControl>

Exemple complet

Bien entendu cette liste n’est pas complète mais vous montre clairement la puissance de ces nouveau concepts d’autant plus que nous pouvons créer nos propres effets  (nous y reviendrons dessus).

Maintenant que vous avez un apercu de ce que nous pouvons faire voici une application complete de présentation de ces principaux concepts : 

3dbehaviourapp


<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
			 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
			 xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
			 xmlns:il="clr-namespace:Microsoft.Expression.Interactivity.Layout;assembly=Microsoft.Expression.Interactions"
			 xmlns:im="clr-namespace:Microsoft.Expression.Interactivity.Media;assembly=Microsoft.Expression.Interactions"
			 x:Class="SilverlightApplication6.MainPage">

	<UserControl.Resources>
		<Storyboard x:Name="RotateStoryboard">
			<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
										   Storyboard.TargetName="RotateRoot"
										   Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)">
				<EasingDoubleKeyFrame KeyTime="00:00:02"
									  Value="1080" />
				<EasingDoubleKeyFrame KeyTime="00:00:04"
									  Value="0">
					<EasingDoubleKeyFrame.EasingFunction>
						<ElasticEase EasingMode="EaseInOut" />
					</EasingDoubleKeyFrame.EasingFunction>
				</EasingDoubleKeyFrame>
			</DoubleAnimationUsingKeyFrames>
		</Storyboard>
	</UserControl.Resources>

	<Grid x:Name="LayoutRoot"
		  Background="#FFF1F1F1">

		<Grid.RowDefinitions>
			<RowDefinition Height="Auto" />
			<RowDefinition Height="Auto" />
			<RowDefinition />
		</Grid.RowDefinitions>
		<StackPanel Orientation="Vertical">

			<TextBlock Text="X"
					   TextWrapping="Wrap"
					   Margin="5,0,0,0"
					   FontWeight="Bold" />

			<Slider VerticalAlignment="Top"
					Maximum="1080"
					Minimum="-1080"
					Value="{Binding RotationX, ElementName=Projection, Mode=TwoWay}"
					Margin="0" />
			<TextBlock Text="Y"
					   TextWrapping="Wrap"
					   Margin="5,0,0,0"
					   FontWeight="Bold" />
			<Slider VerticalAlignment="Top"
					Maximum="1080"
					Minimum="-1080"
					Value="{Binding RotationY, ElementName=Projection, Mode=TwoWay}"
					Margin="0" />
			<TextBlock Text="Z"
					   TextWrapping="Wrap"
					   Margin="5,0,0,0"
					   FontWeight="Bold" />
			<Slider VerticalAlignment="Top"
					Maximum="1080"
					Minimum="-1080"
					Value="{Binding RotationZ, ElementName=Projection, Mode=TwoWay}"
					Margin="0" />
			<TextBlock Text="Zoom"
					   TextWrapping="Wrap"
					   Margin="5,0,0,0"
					   FontWeight="Bold" />
			<Slider VerticalAlignment="Top"
					Maximum="5"
					Minimum="0"
					Value="1"
					Margin="0"
					ValueChanged="Slider_ValueChanged" />
			<Button HorizontalAlignment="Left"
					Margin="0"
					Content="Animation !">
				<i:Interaction.Triggers>
					<i:EventTrigger EventName="Click">
						<im:ControlStoryboardAction Storyboard="{StaticResource RotateStoryboard}" />
					</i:EventTrigger>
				</i:Interaction.Triggers>
			</Button>
		</StackPanel>
		<Rectangle StrokeThickness="0"
				   Height="5"
				   Grid.Row="1">
			<Rectangle.Fill>
				<LinearGradientBrush EndPoint="0.5,1"
									 StartPoint="0.5,0">
					<GradientStop Color="#26000000"
								  Offset="0" />
					<GradientStop Offset="0.862" />
				</LinearGradientBrush>
			</Rectangle.Fill>
		</Rectangle>
		<Grid x:Name="RotateRoot"
			  HorizontalAlignment="Center"
			  VerticalAlignment="Center"
			  Background="#00FF0000"
			  Grid.Row="2"
			  RenderTransformOrigin="0.5,0.5">
			<Grid.Projection>
				<PlaneProjection x:Name="Projection"
								 RotationX="0"
								 RotationZ="0"
								 RotationY="0" />
			</Grid.Projection>
			<i:Interaction.Behaviors>
				<il:MouseDragElementBehavior />
			</i:Interaction.Behaviors>
			<Grid.RowDefinitions>
				<RowDefinition Height="Auto" />
				<RowDefinition />
			</Grid.RowDefinitions>

			<Grid.RenderTransform>
				<TransformGroup>
					<ScaleTransform x:Name="RotateRootScaleTransform" />
					<SkewTransform />
					<RotateTransform />
					<TranslateTransform />
				</TransformGroup>
			</Grid.RenderTransform>

			<TextBlock HorizontalAlignment="Center"
					   Margin="0,0,0,10"
					   VerticalAlignment="Top"
					   Text="Fais moi tourner !"
					   TextWrapping="Wrap" />
			<Border HorizontalAlignment="Center"
					VerticalAlignment="Center"
					Width="150"
					Background="White"
					BorderBrush="White"
					BorderThickness="5"
					Grid.Row="1">
				<Border.Effect>
					<DropShadowEffect Opacity="0.6"
									  ShadowDepth="0" />
				</Border.Effect>
				<StackPanel>
					<Image Source="SilverlightLogo.png" />
					<TextBlock Text="Un petit texte"
							   TextWrapping="Wrap"
							   Margin="0,10,0,0" />
					<Button Content="Button"
							Margin="0,10,0,0" />
					<TextBox Text="TextBox"
							 TextWrapping="Wrap"
							 Margin="0,10,0,0" />

				</StackPanel>
			</Border>
		</Grid>
	</Grid>
</UserControl>

Ainsi que son code behind : 


using System.Windows.Controls;

namespace SilverlightApplication6
{
	public partial class MainPage : UserControl
	{
		public MainPage()
		{
			// Required to initialize variables
			InitializeComponent();
		}

		private void Slider_ValueChanged(object sender, System.Windows.RoutedPropertyChangedEventArgs<double> e)
		{
			if(RotateRootScaleTransform != null)
			{
			RotateRootScaleTransform.ScaleX= e.NewValue;
			RotateRootScaleTransform.ScaleY= e.NewValue;
			}
		}
	}
}

Code source complet de l’application ProjectionBehaviorEffects.zip

Publicités
Catégories :Silverlight, Silverlight 3

[WPF] : Fast thumbnails image generation (+ rotation)

Si vous manipulez des images dans une application WPF vous avez déjà du être confrontés à des problèmes de performances et d’empreinte mémoire importante…
Je partage, dans ce post, mon retour sur expérience dans le domaine !
Voici une solution pour générer rapidement et à la volée des minaitures de vos images tout en ayant une application restant stable lors de la génération des dites miniatures.

FastThumbnails

Tout d’abord nous allons créer un ViewModel représentant une image :


public class PhotoViewModel : INotifyPropertyChanged
    {
        #region Membres
        private BitmapSource _thumbnail = null;
        /// <summary>
        /// INotofyPropertyChanged event
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
        private ExifOrientations orientation = ExifOrientations.Normal;
        #endregion

        #region Ctors
        /// <summary>
        /// Crée une instance du view model permettant d'obtenir un thumbnail d'une image
        /// </summary>
        /// <param name="mediaUrl">Url de l'image à manipuler</param>
        public PhotoViewModel(String mediaUrl)
        {
            MediaUrl = mediaUrl;
        }
        #endregion

        #region Propriétées

        /// <summary>
        /// Détermine l'url de l'image à manipuler
        /// </summary>
        public String MediaUrl
        {
            get;
            private set;
        }

        /// <summary>
        /// Détermine si la génération de la miniature à échoué
        /// </summary>
        public Boolean IsFailed
        {
            get;
            private set;
        }

        /// <summary>
        /// Obtient le thumbnail de l'image source
        /// </summary>
        public BitmapSource Thumbnail
        {
            get
            {
                if ((_thumbnail == null) && !IsFailed)
                    _thumbnail = GetThumbnail();
                return _thumbnail;
            }
        }
        #endregion

        #region Méthodes
        /// <summary>
        /// Libère les ressources utilisées
        /// </summary>
        public void Dispose()
        {
            _thumbnail = null;
        }

        /// <summary>
        /// Obtient le thumnail de l'image source (incluant la rotation)
        /// </summary>
        /// <returns></returns>
        private BitmapSource GetThumbnail()
        {
            BitmapSource ret = null;
            BitmapMetadata meta = null;
            double angle = 0;

            try
            {
                //tentative de creation du thumbnail via Bitmap frame : très rapide et très peu couteux en mémoire !
                BitmapFrame frame = BitmapFrame.Create(
                    new Uri(MediaUrl),
                    BitmapCreateOptions.DelayCreation,
                    BitmapCacheOption.None);

                if (frame.Thumbnail == null) //echec, on tente avec BitmapImage (plus lent et couteux en mémoire)
                {
                    BitmapImage image = new BitmapImage();
                    image.DecodePixelHeight = 90; //on limite à 90px de hauteur
                    image.BeginInit();
                    image.UriSource = new Uri(MediaUrl);
                    image.CacheOption = BitmapCacheOption.None;
                    image.CreateOptions = BitmapCreateOptions.DelayCreation; //important pour les performances
                    image.EndInit();

                    if (image.CanFreeze) //pour éviter les memory leak
                        image.Freeze();

                    ret = image;
                }
                else
                {
                    //récupération des métas de l'image
                    meta = frame.Metadata as BitmapMetadata;
                    ret = frame.Thumbnail;
                }

                if ((meta != null) && (ret != null)) //si on a des meta, tentative de récupération de l'orientation
                {
                    if (meta.GetQuery("/app1/ifd/{ushort=274}") != null)
                    {
                        orientation = (ExifOrientations)Enum.Parse(typeof(ExifOrientations), meta.GetQuery("/app1/ifd/{ushort=274}").ToString());
                    }

                    switch (orientation)
                    {
                        case ExifOrientations.Rotate90:
                            angle = -90;
                            break;
                        case ExifOrientations.Rotate180:
                            angle = 180;
                            break;
                        case ExifOrientations.Rotate270:
                            angle = 90;
                            break;
                    }

                    if (angle != 0) //on doit effectuer une rotation de l'image
                    {
                        ret = new TransformedBitmap(ret.Clone(), new RotateTransform(angle));
                        ret.Freeze();
                    }
                }
            }
            catch (Exception ex)
            {
            }

            return ret;
        }
        #endregion

        #region Handlers
        public void OnPropertyChanged(String propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }

Ensuite créez un nouveau fichier code pour y mettre l’énumération des données Exif d’orientation :


public enum ExifOrientations
    {
        None = 0,
        Normal = 1,
        HorizontalFlip = 2,
        Rotate180 = 3,
        VerticalFlip = 4,
        Transpose = 5,
        Rotate270 = 6,
        Transverse = 7,
        Rotate90 = 8
    }

Ca y est nous y sommes presque !, créons une fenêtre WPF :


<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Background="White">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <Button Content="Parcourrir..." Click="Button_Click"/>
            <Button Content="Clear" Click="Button_Click_1"/>
        </StackPanel>

        <ItemsControl x:Name="UploadList"
                 Grid.Row="1"
                      ItemsSource="{Binding}"
                 ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                 Background="Transparent"
                 ScrollViewer.IsDeferredScrollingEnabled="True">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel IsItemsHost="True"/>

                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid Margin="5" >
                        <Rectangle Fill="White" >
                            <Rectangle.BitmapEffect>
                                <DropShadowBitmapEffect Opacity="0.5" ShadowDepth="0" />
                            </Rectangle.BitmapEffect>
                        </Rectangle>
                        <Border Background="Gray" BorderBrush="White" BorderThickness="5" Height="90">
                            <Image Stretch="Uniform" Source="{Binding Path=Thumbnail, Mode=OneTime, IsAsync=True}"/>
                        </Border>
                    </Grid>

                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

Et voici le code behind :


using System;
using System.Windows;
using Microsoft.Win32;
using System.Collections.ObjectModel;

namespace WpfApplication1
{
    /// <summary>
    /// Logique d'interaction pour Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        ObservableCollection<PhotoViewModel> liste = new ObservableCollection<PhotoViewModel>();

        public Window1()
        {
            InitializeComponent();

            DataContext = liste;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Multiselect = true;
            dlg.Filter = "Fichiers image|*.jpg";
            dlg.ShowDialog(this);

            foreach (String s in dlg.FileNames)
            {
                liste.Add(new PhotoViewModel(s));
            }
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            foreach (PhotoViewModel m in liste)
                m.Dispose();

            liste.Clear();

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
    }
}

Facile et rapide !
Code source de l’application : WPFFastThumbnails.zip (renommez -zip.doc en .zip)

Catégories :WPF

[SL3] : Sélectionner le texte d’un TextBlock

Silverlight est une technologie fonctionnant généralement dans un navigateur (si on ne parle pas du mode Out Of Browser bien sûr). Nous nous devons donc de ne pas trop perturber les habitudes des Internautes pour lequels nous faisons nos applications…
Or j’ai souvent eu la remarque lors de développements Silverlight de la frustration des utilisateurs à ne pouvoir séléctionner un texte visible ! Généralement nous utilisons le controle TextBlock pour afficher du texte, qui ne permet pas de faire la dite sélection…
Alors comment faire ? Il existe plusieurs méthodes pour parvenir à nos fins, la plus simple étant de réaliser un style pour le controle TextBox afin lui donner l’aparence d’un TextBlock ! Pour celà nous retirons les comportements associés au focus, mouseover, etc et nous rendons la TextBox en ReadOnly. Ainsi nous obtenons le style suivant :


<Style x:Key="TextBlockFakeStyle"
			   TargetType="TextBox">
			<Setter Property="BorderThickness"
					Value="0" />
			<Setter Property="IsReadOnly"
					Value="True" />
			<Setter Property="Foreground"
					Value="#FF000000" />
			<Setter Property="Padding"
					Value="0" />
			<Setter Property="Template">
				<Setter.Value>
					<ControlTemplate TargetType="TextBox">
						<Border x:Name="Border"
								Background="Transparent">
							<ScrollViewer x:Name="ContentElement"
										  BorderThickness="0"
										  IsTabStop="False"
										  Padding="{TemplateBinding Padding}" />
						</Border>
					</ControlTemplate>
				</Setter.Value>
			</Setter>
		</Style>

Pour ce qui est de l’utilisation, rien de plus simple : utilisez le controle TextBox en lieu et place d’un TextBlock et appliquez lui le style que nous venons de faire et le tour est joué :


	<Grid x:Name="LayoutRoot"
		  Background="Ivory">

			<TextBox Text="Auxerunt haec vulgi sordidioris audaciam, quod cum ingravesceret penuria commeatuum, famis et furoris inpulsu Eubuli cuiusdam inter suos clari domum ambitiosam ignibus subditis inflammavit rectoremque ut sibi iudicio imperiali addictum calcibus incessens et pugnis conculcans seminecem laniatu miserando discerpsit. post cuius lacrimosum interitum in unius exitio quisque imaginem periculi sui considerans documento recenti similia formidabat."
					 Style="{StaticResource TextBlockFakeStyle}"
					 TextWrapping="Wrap"
					 VerticalAlignment="Top"/>

	</Grid>

Catégories :Silverlight, Silverlight 3

[SL3] : TemplateBinding + Converters…

Petite astuce pour ceux qui comme moi se sont rendus compte qu’on ne pouvait pas utiliser de converters via un TemplateBinding dans le ControlTemplate du Style d’un controle comme il est possible de le faire WPF.
Cependant une solution simple pour palier ce problème est d’utiliser le Relative Binding comme ceci :

En WPF vous auriez écrit :


"{TemplateBinding VotreProprieteDuControle, Converter={StaticResource CleDeVotreConverter}}"

En Silverlight vous devez l’écrire ainsi (fonctionnel sur WPF en prime) :


{Binding RelativeSource={RelativeSource TemplatedParent}, Path=VotreProprieteDuControle, Converter={StaticResource CleDeVotreConverter}}"

Simple et fonctionnel, nous sommes sauvés pour cette fois-ci !

Catégories :Silverlight 3