Blaise Braye, IT Edition

Aller au contenu | Aller au menu | Aller à la recherche

vendredi, 10 février 2012

Positionner et redimmensionner une image en silverlight

En développant un module de visualisation de photos (silverlight 4), Il me fallait un outil capable de redimmensioner une image avec la roulette de la souris et de la repositionner sur une surface via un "drag and drop". J'ai pu obtenir le résultat escompté en lisant le contenu de ce lien , moyennant quelques adaptations propres à mes besoins.

  • la solution proposée utilise certaines fonctions propres à wpf, heureusement elle ne sont pas très complexes à réécrire (substraction de vecteur...)
  • La grande différence entre mon implémentation et celle proposée par l'utilisateur est que j'ai préféré une gestion des événements au niveau de la bordure qui contient l'image. De cette façon, le contrôle sur l'image est possible partout à l'intérieur de la bordure (c'est le comportement d'un calque dans Photoshop). Notons que pour permettre une gestion des événements à un conteneur quelqu'il soit (une bordure ici), il faut impérativement lui imposer une couleur d'arrière plan si son contenu ne prend pas toute la surface disponible. Raison intuitive de cette obligation : les évènements ne se produisent que s'ils sont propagés via un contenu (--> conteneur != contenu).
  • Outre cette différence, j'ai souhaité que le resizing lors de l'événement "MouseWheel" (roulette de la souris) préserve la position de la souris sur l'image. Par exemple, si je zoome avec la souris sur le volant d'une voiture, mon objectif est de préserver le volant de cette voiture sous ma souris.
  • Une dernière fonctionnalité intéressante, retrouvée sur ce lien, permet de s'assurer que le moteur de rendu silverlight n'affiche pas le contenu de la bordure qui se retrouverait en dehors de sa zone d'affichage. Cette fonctionnalité est présente nativement dans WPF et est activée par la propriété "ClipToBound"

Code Xaml

<Border Grid.Row="1" Layout:Clip.ToBounds="True" Background="WhiteSmoke"
        MouseWheel="OnBorderMouseWheel"
        MouseLeftButtonDown="OnBorderMouseLeftButtonDown"
        MouseLeftButtonUp="OnBorderMouseLeftButtonUp"
        MouseMove="OnBorderMouseMove"
        >
    <Image x:Name="imgLarge" RenderTransformOrigin="0.5,0.5"      
        HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None" Source="{Binding Value.FileWebUrl}">
    <Image.RenderTransform>
        <TransformGroup>
            <ScaleTransform />
            <TranslateTransform/>
        </TransformGroup>
    </Image.RenderTransform>
</Image>
</Border>

Code C# associé

/// <summary>
/// Called when [border mouse wheel].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseWheelEventArgs"/> instance containing the event data.</param>
private void OnBorderMouseWheel(object sender, MouseWheelEventArgs e)
{
    var border = (Border)sender;
    var image = (Image)border.Child;

    var st = ((TransformGroup)image.RenderTransform).Children.OfType<ScaleTransform>().First();
    var tt = ((TransformGroup)image.RenderTransform).Children.OfType<TranslateTransform>().First();

    var start = e.GetPosition(image);
    var origin = new Point(tt.X, tt.Y);

            
    double zoom = e.Delta > 0 ? .2 : -.2;
    st.ScaleX += zoom;
    st.ScaleY += zoom;

    var target = e.GetPosition(image);

    var v = st.Transform(Substract(start,target));
    tt.X = origin.X - v.X;
    tt.Y = origin.Y - v.Y;

}

Point _start;
Point _origin;
bool _isMouseCaptured;

/// <summary>
/// Called when [border mouse left button down].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
private void OnBorderMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var border = (Border)sender;
    var image = (Image)border.Child;
            
    var tt = ((TransformGroup)image.RenderTransform).Children.OfType<TranslateTransform>().Single();
    _start = e.GetPosition(border);
    _origin = new Point(tt.X, tt.Y);

    _isMouseCaptured = true;
    image.CaptureMouse();
}

/// <summary>
/// Called when [border mouse left button up].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
private void OnBorderMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    var border = (Border)sender;
    var image = (Image)border.Child;
    _isMouseCaptured = false;
    image.ReleaseMouseCapture();
}

/// <summary>
/// Called when [border mouse move].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
private void OnBorderMouseMove(object sender, MouseEventArgs e)
{
    var border = (Border)sender;
    var image = (Image)border.Child;

    if (_isMouseCaptured)
    {
        var tt = (TranslateTransform)((TransformGroup)image.RenderTransform)
            .Children.First(tr => tr is TranslateTransform);

        var v = Substract(_start, e.GetPosition(border));
        tt.X = _origin.X - v.X;
        tt.Y = _origin.Y - v.Y;
    }
}

/// <summary>
/// Substracts the specified PT1.
/// </summary>
/// <param name="pt1">The PT1.</param>
/// <param name="pt2">The PT2.</param>
/// <returns></returns>
public static Point Substract(Point pt1, Point pt2)
{
    return new Point(pt1.X - pt2.X, pt1.Y - pt2.Y);
}

jeudi, 8 décembre 2011

Debug Tools pour travailler avec WCF

Je viens de passer deux jours sur un problème qui aurait pu être découvert instantanément si j'avais utilisé les bons outils tout de suite. (silverlight vs wcf ria services)

Généralement, les erreurs sont assez simple à obtenir via des points d'entrées standard tels qu'une récupération systématique des exceptions non gérées au niveau de l'application. Néanmoins, quand le problème vient d'une couche inférieure (data, transport), cela peut se corser sévèrement. il est possible de diagnostiquer rapidement les symptomes à l'aide de l'outil Fiddler. Une fois chargé, ce dernier écoute tous les allers-retours partant de votre client web. Mais certaines erreurs sont très sourdes : erreur 504, le serveur ne retourne pas de réponses...??? comment est-ce possible? a-t-on déjà vu une requête web sans réponse? à moins d'un problème sur la bande, ce devrait être impossible... Mais voilà, Microsoft a réussi à faire ça dans son conception des services wcf --> si une exception se produit à ce niveau, on en entend pas parler, on n'est même pas au courant qu'il y a un soucis à ce niveau. BRAVO.

Bref, une fois ce fait acquis, on cherche dans cette direction et on trouve rapidement ce type de post: wcf-ria-services-not-found-error-message.aspx et là, on se sent très bête... Cet outil devrait être l'un des premiers à prendre en main lorsque l'on développe avec des services WCF, c'est indispensable!

MS n'est certainement pas à blâmer dans cette histoire, alors Blaise, si tu développes des services WCF, il faut utiliser "Microsoft Trace Viewer" dès que Fiddler te parle d'une erreur 504!