Month: September 2009

Funky Popups in Silverlight 3

An old post of mine, Funky Popups using Tweener, showed how you could use the Tweener library in SilverlightContrib to make some interesting transitions.

When Silverlight 3 was released, they added these kinds of tweening functions into the core runtime, so it’s probably time to look at how to use them.

In the original code, our storyboard for one of the items looked like this:


<DoubleAnimationUsingKeyFrames
 Storyboard.TargetName="bounceScaleTransform"
 Storyboard.TargetProperty="ScaleX"
 tween:Tween.From="0.6"
 tween:Tween.To="1"
 tween:Tween.Fps="10"
 tween:Tween.TransitionType="EaseOutBounce"
 Duration="0:0:0.5"/>

Here’s how the equivalent using Silverlight 3’s easing functions. One important different is that we’re not forced to use a keyframe animation any more.


<DoubleAnimation
 Storyboard.TargetName="bounce3ScaleTransform"
 Storyboard.TargetProperty="ScaleX"
 From="0.6"
 To="1"
 Duration="0:0:0.5">
 <DoubleAnimation.EasingFunction>
 <BounceEase EasingMode="EaseOut" Bounces="1" Bounciness="4"/>
 </DoubleAnimation.EasingFunction>
 </DoubleAnimation>

In place of the tweener attributes is the EasingFunction which can be one of several kinds. To simulate the old bounce behaviour we’ve used a BounceEase function and set EaseOut for the easing mode. The attributes Bounces and Bounciness can adjust the amount of bounce. These values get close to the Tweener behaviour and are fine for our behaviour.

The Swish animation can be similarly replaced by BounceEase animations.

The updated project can be downloaded from here.

Scale other content on top of a Deep Zoom Image

Marthinus asked, in a comment:

“is there ANY way to overwrite this, so that I can make the msi use a multiscalesubimage WITH an extra canvas ontop (which would then move and scale WITH the subimage)?”

Now, I’ve never done overlays with collections and sub-images, and one thing you definitely can’t do is interleave other content with subimages of a deep zoom image (since the MultiScaleImage is a single UI element). But I have done things which have locked an overlay panel to the movement of the MultiScaleImage. Here’s a sample. First, the Xaml.


<UserControl x:Class="DeepZoomCanvas.MainPage"
 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" d:DesignWidth="640" d:DesignHeight="480">
 <Grid x:Name="LayoutRoot">
 <MultiScaleImage x:Name="msi" Source="http://www.uslot.com/ClientBin/dzc_output.xml"/>
 <Grid 
 IsHitTestVisible="False"
 x:Name="overlay">
 <Grid.RenderTransform>
 <TransformGroup>
 <ScaleTransform x:Name="overlayScale"/>
 <TranslateTransform x:Name="overlayTranslate"/>
 </TransformGroup>
 </Grid.RenderTransform>
 <Border 
 CornerRadius="8" 
 Background="#44FFFFFF" 
 VerticalAlignment="Center" 
 HorizontalAlignment="Center" 
 Padding="30">
 <StackPanel>
 <TextBlock 
 FontFamily="Arial" 
 FontSize="24" 
 MaxWidth="400" 
 FontWeight="Bold" 
 TextWrapping="Wrap">
 This shows some elements which 
 scale and translate locked to 
 the underlying MultiScaleImage. 
 Any layout items can be used here.
 </TextBlock>
 <Border Background="#55FF0000" Padding="10" HorizontalAlignment="Center" VerticalAlignment="Top">
 <StackPanel>
 <TextBlock 
 FontFamily="Arial"
 FontSize="0.1"
 >This is a tiny line of text which still scales in concert with the deep zoom image.</TextBlock>
 </StackPanel>
 </Border>
 </StackPanel>
 </Border>
 </Grid>
 </Grid>
</UserControl>

And the corresponding C# code:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace DeepZoomCanvas
{
 public partial class MainPage : UserControl
 {
 public MainPage()
 {
 InitializeComponent();
 msi.MouseLeftButtonDown += new MouseButtonEventHandler(msi_MouseLeftButtonDown);
 msi.MouseMove += new MouseEventHandler(msi_MouseMove);
 msi.MouseLeftButtonUp += new MouseButtonEventHandler(msi_MouseLeftButtonUp);
 msi.MouseWheel += new MouseWheelEventHandler(msi_MouseWheel);

 // Track changes to the multi scale image
 msi.ViewportChanged += new RoutedEventHandler(msi_ViewportChanged);
 }

 /// <summary>
 /// This is the code which locks the overlay to the underlying deep zoom image.
 /// All it really does is set the scale factor and offset of the overlay
 /// based on the current setting of the deep zoom image.
 /// </summary>
 ///
<param name="sender">event sender</param>
 ///
<param name="e">event args</param>
 void msi_ViewportChanged(object sender, RoutedEventArgs e)
 {
 // This event is called during animations of the image.
 // Match the scaling of the canvas with the image
 Point viewportOrigin = msi.ViewportOrigin;
 double viewportWidth = msi.ViewportWidth;

 // The scale factor is just the inverse of the ViewportWidth
 overlayScale.ScaleX = 1 / viewportWidth;
 overlayScale.ScaleY = 1 / viewportWidth;

 // The offset is calculated by finding the location of the origin of the dzi
 // in element coordinates.
 Point newO = LogicalToElement(new Point(), viewportOrigin, viewportWidth);
 overlayTranslate.X = newO.X;
 overlayTranslate.Y = newO.Y;
 }

 private Point LogicalToElement(Point p, Point Origin, double Width)
 {
 return new Point(((p.X - Origin.X) / Width) * msi.ActualWidth,
 ((p.Y - Origin.Y) / Width) * msi.ActualWidth);
 }

 public Point ElementToLogical(Point p, Point Origin, double Width)
 {
 return new Point(Origin.X + (p.X * Width) / msi.ActualWidth,
 Origin.Y + (p.Y * Width) / msi.ActualWidth);
 }

#region Mouse handling
 void msi_MouseWheel(object sender, MouseWheelEventArgs e)
 {
 if (e.Delta < 0)
 {
 Point logicalPoint = msi.ElementToLogicalPoint(lastMousePos);
 msi.ZoomAboutLogicalPoint(0.8, logicalPoint.X, logicalPoint.Y);

 }
 else
 {
 Point logicalPoint = msi.ElementToLogicalPoint(lastMousePos);
 msi.ZoomAboutLogicalPoint(1.2, logicalPoint.X, logicalPoint.Y);
 }
 }

 void msi_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
 {
 if (IsMouseDown)
 {
 msi.ReleaseMouseCapture();
 if (IsDrag == false)
 {
 if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
 {
 Point logicalPoint = msi.ElementToLogicalPoint(lastMousePos);
 msi.ZoomAboutLogicalPoint(0.8, logicalPoint.X, logicalPoint.Y);
 }
 else
 {
 Point logicalPoint = msi.ElementToLogicalPoint(lastMousePos);
 msi.ZoomAboutLogicalPoint(1.2, logicalPoint.X, logicalPoint.Y);
 }
 }
 IsMouseDown = false;
 IsDrag = false;
 }
 }

 void msi_MouseMove(object sender, MouseEventArgs e)
 {
 lastMousePos = e.GetPosition(msi);
 if (IsMouseDown)
 {
 IsDrag = true;
 }
 if (IsDrag)
 {
 Point newPoint = lastMouseViewPort;
 newPoint.X += (lastMouseDownPos.X - lastMousePos.X) / msi.ActualWidth * msi.ViewportWidth;
 newPoint.Y += (lastMouseDownPos.Y - lastMousePos.Y) / msi.ActualWidth * msi.ViewportWidth;
 msi.ViewportOrigin = newPoint;
 }
 }

 bool IsMouseDown = false;
 bool IsDrag = false;
 private Point lastMouseDownPos = new Point();
 private Point lastMouseViewPort = new Point();
 private Point lastMousePos = new Point();

 void msi_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 {
 IsMouseDown = true;
 msi.CaptureMouse();
 lastMouseDownPos = e.GetPosition(msi);
 lastMouseViewPort = msi.ViewportOrigin;
 }

 #endregion    
 }
}

All the work is done in the ViewportChanged event handler (which fires every time the viewport changes, even during animations). I’ve set a scale transform and a translate transform on the grid (which could be a canvas if you want) and we adjust the scale and translate values to match the underlying image.

All the rest of the code is just bog-standard deep zoom image mouse handling.

Try zooming into the red square to see a very small line of text.

Hope this helps.

Why I Love Derren Brown

This week, Derren Brown predicted the lottery numbers.

Well, that’s what he said he was going to do, and that’s what he presented. He showed a set of six balls, kept them in full view throughout the live lottery broadcast, then revealed that the numbers on the other side of the balls were the winning numbers. Then he said his show on Friday would show how he did it.

Following the ‘revelation’ a lot of people are disappointed. They genuinely believed he would reveal the technique he used.

Strangely, they’re disappointed because they’ve believed everything he’s ever said previously, when the only thing they should actually believe is his standard ‘disclaimer’: That he uses ‘magic, suggestion, psychology, misdirection and showmanship’.

The prediction was a traditional mentalist prediction in every form. The performer has a ‘prediction’ which he places somewhere he can’t tamper with it, the event happens, then the performer reveals his prediction. This has been the form of this trick for decades.

I don’t think I’m revealing any deep secrets when I say that it’s impossible to guess the numbers, and almost impossible to accurately rig a lottery machine. So if you discount those possibilities, all that’s left is how to get the numbers onto the balls after they’ve come up on the broadcast. There are several ways this could have been done, ranging from an enormously complex split screen camera effect (which I doubt because it would break Derren’s ‘rules’) to electronic balls, laser etching, projection or (my theory) a hidden printing mechanism under the balls.

But how he did the actual trick isn’t really important. It was a perfectly performed illusion, and got a lot of people watching. Which is good, I think.

But the ‘revelation’ on Friday was problematic for me. I was fairly sure he wouldn’t go anywhere near the actual technique (because, frankly, magical techniques are clever, but ultimately mundane, compared to the effect they have). So how would he fill an hour while a) not talking about how he did it, and b) not outright lying.

I have a lot of respect for Derren. I think he’s one of the good guys. His book, Tricks of the Mind, was a fascinating look at mind, belief, memory and ended up almost as a fluffier version of Dawkins’ ‘The God Delusion’. And he’s never claimed to have (or believe in) psychic powers, even when presenting effects that seem to require them.

In ‘The System’ he appeared to be saying that it was possible to predict horse races, but the revelation was actually satisfying (topped off by a genuine magic trick). In one of his Tricks of the Mind he appeared to have hypnotised someone into being able to play piano like a virtuoso. I grew more and more uncomfortable with that show, as I knew it was impossible, but the revelation at the end actually moved me to tears.

I have an expectation that Derren will ‘play fair’ with the audience. Within the context of a magical performance, that is, where you expect the performer to declare that he’s doing something that appears impossible.

So I do get uncomfortable when he starts talking about the PEAR experiments. These were experiments performed (in part) to detect psychokinesis, the ability to affect the random behaviour of a machine using the mind. They initially said they were successful, but later analyses showed that their results weren’t as good as they suggested.

Derren then showed a nice trick with coin tossing that appeared to show an effect, but then explained it was a maths trick. Then, significantly, he said something like ‘and it turned out that the PEAR results didn’t really stand up’. Now, he said that casually, almost throwaway, so most of the audience probably didn’t really notice, so the strong suggestion that it’s possible to affect things with the mind remained, but he did that while still telling the truth about the experiments. That pleased me a lot.

Then came the main part of the programme, where he had a group of people harnessing ‘the wisdom of crowds’ to predict the lottery numbers. I’ve got an idea how this was done, but it’s enough to say this was another trick. But basically, this was just another piece of misdirection.

Then, just as you think he’s saying that’s how he did it, he did a very funny description of how he might have fixed the lottery machine.

And right at the end, with an almost straight face, he said ‘So I couldn’t possibly admit that I fixed the machine – which I didn’t – so now, when people ask me how I did it, I’ll just say “It was a trick”.’

Perfect. He managed a whole hour of fun and misdirection, and yet he genuinely did say how he did it.

And that’s why I love Derren Brown.

Why don’t I get MouseUp events in the Bing Maps Silverlight Component

The Bing Maps Silverlight component (currently in CTP) allows you to overlay items on the map, but it does some odd things to mouse events. Take the following Xaml:


<UserControl x:Class="BingBlogPost.MainPage"
 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"
 xmlns:map="clr-namespace:Microsoft.VirtualEarth.MapControl;assembly=Microsoft.VirtualEarth.MapControl"
 mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
 <Grid x:Name="LayoutRoot">
 <map:Map>
 <Border Padding="8"
 Background="Gray"
 HorizontalAlignment="Center"
 VerticalAlignment="Center"
 MouseLeftButtonUp="Message_Click"
 >
 <TextBlock Text="Click here"/>
 </Border>
 </map:Map>
 </Grid>
</UserControl>

This will show the world map, with our ‘control’ in the centre. Now, mouse handling is interesting using the map component. You’ll notice that if you click and drag on the control, the map underneath will drag. In this example we’ve got an event handler which is set to receive the button up event (to simulate a mouse click). here’s the event handling code:

private void Message_Click(object sender, MouseButtonEventArgs e)
{
     MessageBox.Show(“Clicked!”);
}

But you’ll notice that this event doesn’t fire on a single click. Oddly, it does fire after a double-click. So clearly, the map itself is doing something tricky with events.

If you want to get a click event, there’s two things you can do. You could put in an actual button. This handles mouse events properly and will pass on its own Click event as you’d expect. But if you want to roll your own, there’s a simple trick: add a handler on the control for MouseLeftButtonDown and handle it with this code:

private void Message_MouseDown(object sender, MouseButtonEventArgs e)
{
     e.Handled = true;
}

This is enough to stop the map eating the mouse down event and capturing the mouse (which is why you never get the event). You’ll get the Up event as you expect and you can handle it normally.