onsdag 2 december 2009

ISurfaceScrollInfo and you, Part Two.

It’s been a while since I wrote the last post, but blame on the flu. But now I have the energy to continue this “blog series”.

In the last post I talked about how to generally create a custom panel in WPF and I also showed how I implemented MeasureOverride and ArrangeOverride for our SurfacePagePanel. Now I will continue this blog series with a part of the ISurfaceScrollInfo interface. I will actually start with looking at the IScrollInfo, which ISurfaceScrollInfo extends from.

Before diving into the IScrollInfo interface I will post a few reference links which have helped me. Read the reference links because there’s a lot of information there:

IScrollInfo, when implemented, tells a ScrollViewer how a particular panel is scrolled. If a panel doesn’t implement IScrollInfo the ScrollViewer will scroll the panel according to some default behavior. Before jumping in how to implement IScrollInfo I want to explain a couple of concepts you need to understand:

  • Viewport
  • Extent

Viewport is the area of the panel that is visible to the user. Looking at figure 1 the Viewport represents the red solid rectangle. For instance, in our case the SurfacePagePanel is suppose to reside within a SurfaceListBox. The SurfaceListBox controls how much we are able to see of it items and thus it’s size will implicitly be our Viewport.

The Extent on the other hand is the total area all measured items and it’s seen as the dotted rectangle in figure 1. If we once again look at our case, arranging 10 items horizontally where each item is 300 pixels wide and 300 pixels in height will give us an Extent which is 3000 pixels wide (10 items times 300 pixels) and 300 pixels in height. In other words, the Extent is the total area needed to display all items at once.

viewport_extent2figure 1: Viewport (red rectangle) and Extent (dotted rectangle).
The gray rectangles represents items.

If we continue looking at the members of the IScrollInfo interface we see there’s a lot of them. However, many of the members are scrolling methods (methods which are called for certain user actions):

  • MouseWheelUp()
  • LineUp()
  • PageUp()
  • Etc…

Considering a Surface context these methods are not that important (mainly because you need a mouse and keyboard to access these methods) so these methods doesn’t have an implementation.

Moving on to the IScrollInfo members that composes the real scrolling logic:

  • ViewportWidth – The width of the Viewport
  • ViewportHeight – The height of the Viewport
  • ExtentWidth – The width of the Extent
  • ExtentHeight – The height of the Extent
  • VerticalOffset – How much the Viewport is offset vertically according to the upper right corner of the Extent
  • HorizontalOffset – How much the Viewport is offset horizontally to the upper right corner of the Extent
  • SetVerticalOffset – Sets the Viewports vertical offset
  • SetHorizontalOffset – Sets the Viewports horizontal offset
  • CanVerticalScroll – Whether the panel can scroll it’s content vertically
  • CanHorizontalScroll – Whether the panel can scroll it’s content horizontally
  • MakeVisible – Scrolls a specific item (specified as a Visual) to a desired location (specified as a rectangle).

Now, let’s go through how these methods are implemented in the SurfacePagePanel. As you might imagine, the Viewport and Extent is calculated during the measuring pass:

152 protected override Size MeasureOverride(Size availableSize)

153 {

154 var resultSize = new Size(0, 0);

155 var extent = new Size(0, 0);

156

157 foreach (UIElement child in Children)

158 {

159 child.Measure(availableSize);

160 resultSize.Width = Math.Max(resultSize.Width,

161 child.DesiredSize.Width);

162 resultSize.Height = Math.Max(resultSize.Height,

163 child.DesiredSize.Height);

164 extent.Width += child.DesiredSize.Width;

165 }

166

167 resultSize.Width = double.IsPositiveInfinity(availableSize.Width)

168 ? resultSize.Width : availableSize.Width;

169 resultSize.Height = double.IsPositiveInfinity(availableSize.Height)

170 ? resultSize.Height : availableSize.Height;

171 extent.Height = resultSize.Height;

172

173 if ((_viewport != resultSize _extent != extent)

174 && ScrollOwner != null)

175 {

176 _viewport = resultSize;

177 _extent = extent;

178

179 ScrollOwner.InvalidateScrollInfo();

180 }

181

182 return resultSize;

183 }

Code 1: Viewport and Extent calculated in MeasureOverride.

I hope you can read the code, but there isn’t much space with this blogspot theme. But if you can read it, the Viewport is simply the availableSize given to us (or the size of the largest child element in case of infinitive availabeSize). The Extent, on the other hand, is actually calculated. Extent’s width is the sum of all the child elements measured width (row 164) and the height is simply the height of the resultSize (row 171) which indirectly is the height of the Viewport (row 176).

When both the Viewport and the Extent is calculated ViewportWidth, ViewportHeight, ExtentWidth and ExtentHeight are easily implemented as they just return the value of the corresponding properties of Viewport and Extent.

As our SurfacePagePanel only can scroll horizontally CanVerticalScroll is set to false, VerticalOffset always returns 0 and SetVerticalScroll is not implemented. Aa a side note: at first I threw a NotImplementedException from the SetVerticalScroll method but the fact is SetVerticalScroll is called at least once by ScrollViewer. So don’t go throwing NotImplementedException everywhere because you never know if or when it hits you in the face.

Let’s look at the corresponding properties and methods for the horizontal behavior. As you might’ve expected it’s the SetHorizontalOffset that controls the position of the Viewport. To control the Viewport offset a translation transform is used as the panels RenderTransform. Changing the translation transform also changes what is seen through the Viewport. As seen in the code below, SetHoriztonalOffset validates the input and calls the SetViewport method, which is a general method for setting the Viewport.

501 public void SetHorizontalOffset(double offset)

502 {

503 if (!CanHorizontallyScroll)

504 {

505 return;

506 }

507

508 if (offset == _viewportOffset.X)

509 {

510 return;

511 }

512

513 SetViewport(offset, _viewportOffset.Y);

514 }

Code 2: Implementation of SerHorizontalOffset.

578 private void SetViewport(double newHorizontalOffset,

579 double newVerticalOffset)

580 {

581 //Cap the offset values.

582 newHorizontalOffset = Math.Max(0,

583 Math.Min(newHorizontalOffset,

584 ExtentWidth - ViewportWidth));

585 newVerticalOffset = Math.Max(0,

586 Math.Min(newVerticalOffset,

587 ExtentHeight - ViewportHeight));

588

589 _viewportOffset = new Point(newHorizontalOffset, newVerticalOffset);

590 _renderTransform.X = -_viewportOffset.X;

591 _renderTransform.Y = -_viewportOffset.Y;

592

593 if (ScrollOwner != null)

594 {

595 ScrollOwner.InvalidateScrollInfo();

596 }

597 }

Code 3: Implementation of SetViewport.

Seen in the code for SetViewport the ScrollOwner is notified about the changes by calling InvalidateScrollInfo. This is important to keep the ScrollViewer in sync with the panels scrolling data.

To summarize this post we talked about the Viewport and Extent and their roll in scrolling a panels content. I also showed the code for how the scrolling, or the placement of the Viewport, is implemented in the SurfacePagePanel using translate transforming.

Next post will be about the ISurfaceScrollInfo, I promise!

torsdag 12 november 2009

Helping Hands

The last two months I have been working with a team at Ergonomidesign to create a new Surface Application. It is called Helping Hands and envisions the future of integrated health care. How do you, as a potential patient, prevent your lifestyle from becoming an illness? How can you recognize and prevent e.g. Coronary Artery Disease, before it is too late? Welcome to have a look at the future of patient management and treatment in 2015.

The application will exhibit at the world’s largest medical trade fair, Medica/Compamed in Germany next week and by then we will be able to say and show a lot more of the application.

The team creating Helping Hands consists of graphical designers, interaction designers and developers from Ergonomidesign and developers and architects from Connecta.

Right now I can only show you a glimpse of what’s in the application so more is to come during next week!

Ergonomidesign_Future_of_Health_Care_no_logo

 

Also check out the extremely cool custom byte tag with the look of a dragon!

bild

torsdag 29 oktober 2009

ISurfaceScrollInfo and you, Part One.

Before getting into detail about how to implement the ISurfaceScrollInfo, I want to talk about creating a custom panel. But why create a custom panel? Of course there are several ways (as usual) to accomplish a behavior like the page panel, but I think creating a new panel is the best way to take advantage of the WPF framework.

The idea of implementing a custom panel is that we can use it with a ListBox control, SurfaceListBox to be more specific. By implementing a custom panel for a ListBox, we can actually tell the ListBox how we want to layout it’s items.

Now to creating a custom panel. In general, when a panel displays it’s content it does two things: measuring and arranging. First everything in the panel is measured. Measuring is needed to determine the size of the panel and the size depends on one thing: the sizes of the containing items. It is during measuring we have the chance to determine how much space we need to layout the content. In WPF this is the moment when the items desiredSize is set.

After all items are measured, they are arranged. The point of arranging is quite obvious. This is the time when we place each item relatively to each other. Where items are placed depends heavily on their size and that’s why measuring is done before the arrangement. The arrangement is “view independent”, which means you don’t have to think about where you place the items in respect of what is actually viewed to the user. In our case, this is taken care of by SurfaceScrollViewer and that’s the whole point of implementing ISurfaceScrollInfo later on.

To control the measurement and arrangement there are two methods that needs to be overridden in our custom panel:

  • protected override Size MeasureOverride(Size availableSize)
  • protected override Size ArrangeOverride(Size finalSize)

Big surprise huh? As you see MeasueOverride receives a size which describes the available space we have to layout our items. Constrains with other words. Here’s an example: a ListBox which measures 300 times 80 gives an availableSize of 298 times 76. The size returned from the method is the space we want (we may or may not get it). In our implementation only basic measurements are done:

protected override Size MeasureOverride(Size availableSize)
{
var resultSize = new Size(0, 0);

foreach (UIElement child in Children)
{
child.Measure(availableSize);
resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
, }

resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ? resultSize.Width : availableSize.Width;
resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ? resultSize.Height : availableSize.Height;

return resultSize;
}

It’s important that we call Measure on each child or else we won’t have any desired sizes. We can also see that we tell WPF that we need the same space as given to us to layout our items. (Notice the safety precaution if we get infinite available size. It can happen.)

Now for arranging the items. The argument here is the size that WPF is willing to give us and, as before, it may or may not be equal to the size we wanted earlier. In this implementation all items are placed horizontally. Nothing fancy:

protected override Size ArrangeOverride(Size finalSize)
{
if (Children.Count == 0)
{
return finalSize;
}

var startOffset = 0;
foreach (UIElement child in Children)
{
var destination = new Rect(startOffset, 0.0, child.DesiredSize.Width, child.DesiredSize.Height);
child.Arrange(destination);
startOffset += child.DesiredSize.Width;
}

return finalSize;
}
Here we return the same size as given to us. The documentation only say: “The actual size used.” but I think this is probably important when doing more advanced layouting. In our case returning the same size works fine.

That’s all we need to do for measuring and arranging our items. Next post we will start looking at the ISurfaceScrollInfo interface! Stay tuned.

tisdag 20 oktober 2009

Surface developing in Visual Studio 2010

Today it was time to install Visual Studio 2010 Beta 2 and my hope was to be able to develop Surface Application on the new and sweet Beta 2. Installation of Visual Studio 2010 on a clean Windows 7 was really smooth and easy. The installation was fast and I installed everything except C++ since it is not my mother tongue. Since Team Explorer now is included in Visual Studio 2010 Ultimate that also is a time saver! Not to mention not having to install SP1 on Visual Studio 2008 and .NET Framework 3.5 that takes a lot of time!

After the installation of Visual Studio 2010 I installed the XNA Framework Redistributable 2.0. And then it was time to time for Microsoft Surface SDK 1.0 SP1, Workstation Edition. Now to the first problem. In that installation file there is a startup check that needs to be modified. The check looks for Visual Studio 2008 and that can be handled the same way as I handled installing Surface SDK 1.0 on Windows 7 Beta. So read that post and download the vbs-file since it will be needed!

Drag the SurfaceSDKWE-file and drop it on the vbs-file. This will remove the startup-check for Visual Studio 2008 and will let you install the Surface SDK without having Visual Studio 2008.

After the installation of the Surface SDK is finished you have to copy the templates from \Program Files\Microsoft SDKs\Surface\v1.0\Project Templates\ and \Program Files\Microsoft SDKs\Surface\v1.0\Item Templates and place them in the corresponding folders under \Users\surface.developer\Documents\Visual Studio 2010\Templates\. Just copy the files there and don’t unzip them.

Start Visual Studio 2010, click on New Project and create a new project based on the Surface Application (WPF) that is placed under Visual C#. Make sure to select .NET Framework 3.5 before clicking OK!

When I tried to open the project that I’m working on right now I was hit by the project upgrade wizard that wants to upgrade the solution file from 2008 to 2010. This stops med from start using Visual Studio 2010 until the whole team upgrades. This is really sad since it won’t happen at the moment. So the way Visual Studio handles solution files hinders partial team upgrade.

There still might be things that won’t work when developing Surface applications on Visual Studio 2010 and if you find anything please let me know!

ISurfaceScrollInfo and you, Part Zero.

I’ve been working on this secret project with a colleague of mine, and one part of the project is to create a custom panel for the Surface. So what’s so special about the panel I’m doing? I’m want to replicate the “page panel” found on the iPhone and the Android phones. To clarify, I do not know the official name of the panel but I will call it page panel until someone either tell me the official name or come up with a better name. 

So what is the definition of a page panel? I would say these points would define it:

  • Only one item has focus. Other items might be seen but only one will be focused. I will explain in more detail what focus really mean when the time comes.
  • When the focus moves to another item the panel is animated. Just like the mobile versions (see the youtube-link above).
  • The items are arranged on a straight line, either horizontally or vertically. No wrapping with other words. I will implement horizontal support because that’s what I want, but it shouldn’t be any problem implementing vertical arrangement as well, both from a developers and a user experiences point of view.
  • Changing the item focus are done by the user using finger contacts (or any contact?). I have proposed to trigger focus change contact up, if the contact is out of a certain bound. We’ll get to this later.

A custom panel with custom scrolling, or to be more concrete: inherit from Panel and implement the ISurfaceScrollInfo interface. The guys at the Surface Community recommended me to look at the LoopingPanel, which does exactly that!

I have decided to split my story into several parts, just to make it more exciting and to avoid writing the longest blog post in the history of man. This part is an introduction. The next part will be about implementing a custom Panel, but later on I will tell you about implementing the ISurfaceScrollInfo.

To be continued…

lördag 17 oktober 2009

Multi touch in WPF 4.0 and VS2010

Touch and multitouch is something that has been dear to my heart last year. It all started when Connecta bought a Microsoft Surface table for almost a year ago. Even then, Microsoft talked about the need to work with, and learn multi-touch, and now we see Windows 7 and WPF 4.0 come with native support for multi-touch.

WPF 4.0 has added a number of events to the UIElement that relates to muti touch. The .NET APIs are based on native Win32 APIs that are available only in Windows 7. UIElement is the base class that defines the essence of visual controls for layout, input and events. The events that exist today (Beta 1 of VS2010) are: ManipulationStarted Event,
ManipulationCompleted Event,
Manipulation Delta Event,
ManipulationInertiaStarting Event and ManipulationBoundaryFeedback Event.

By default, a UIElement never recieves events relating to manipulation unless Manipulation Mode is set to a value other than None. With the help of Manipulation mode you can control the type of manipulation you can make on an item. It can for instance be translated in X and/or Y axis, rotate, or scale.

In beta 1 of. NET Framework 4.0 the are events for manipulationare are there but there are not any WPF controls that make use of them. In future versions it will be easy just turn on the touch controls on eg sliders or scrollviewers directly in XAML.

When the ManipulationDelta event is captured in a Window you get a ManipulationDeltaEventArgs. That can be used to find out what changes have occurred on the item manipulated. With the method GetDeltaManipulation you get a System.Windows.Input.Manipulation which contains all of the transformation data that was available when the event occurred. The information can then be used to perform translatation operations on the object. A bit complex, but a piece of code probably makes it easier to understand!

Please note that the ManipulationDelta event will never fire with an ordinary mouse. There must be some kind of touch device. With the help of a project on CodePlex called Multi-Touch Vista you can simulate the use of multiple touch points with normal mouse or a touch pad.

In the XAML-file I add an Image to the Canvas. To that I connect a MatrixTransform so that I can access it from code. I also add an event handler for the ManipulationDelta.

<Window   x:Class="WpfApplication13.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
WindowState="Maximized" ManipulationDelta="Window_ManipulationDelta">
<Window.Resources>
<MatrixTransform x:Key="InitialMatrixTransform">
<MatrixTransform.Matrix>
<Matrix OffsetX="200" OffsetY="200"></Matrix>
</MatrixTransform.Matrix>
</MatrixTransform>
</Window.Resources>
<Canvas>
<Image Width="100" Source="/WpfApplication13;component/Images/Sonicspree.jpg" ManipulationMode="All" RenderTransform="{StaticResource InitialMatrixTransform}" ></Image>
</Canvas>
</Window>


In the code behind I use DeltaManipulation to move the image based on input:



private void Window_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
var delta = e.GetDeltaManipulation(this);
var image = e.OriginalSource as Image;
var matrix = ((MatrixTransform)image.RenderTransform).Matrix;
var originalCenter = new Point(image.ActualWidth / 2, image.ActualHeight / 2);

//Translate on the x and y-axis
matrix.Translate(delta.Translation.X, delta.Translation.Y);

//Get the new center point and rotate around that based on the delta
var center = matrix.Transform(originalCenter);
matrix.RotateAt(delta.Rotation, center.X, center.Y);
center = matrix.Transform(originalCenter);

//Scale the matrix based on the delta
matrix.ScaleAt(delta.Scale, delta.Scale, center.X, center.Y);

//Apply the new MatrixTransform
image.RenderTransform = new MatrixTransform(matrix);

e.Handled = true;

}


Note the two red dots representing the two mice I have connected to this computer. The code in this example is based on a video on You Tube that also shows the application in action.



MultiTouchVS2010 


 

måndag 5 oktober 2009

StartupUri and you.

Let me start with introducing myself. My name is Joakim Rosendahl and I’ve been at Connecta since April first 2009. Became a member of Connecta’s Surface team a month later. I have an academic background in Computer Science and I’ve mostly been working with WinForms and WPF since 2007.

Enough about me, because I’m going to talk about something that happened to me in my latest Surface project. I had the ambition to implement a surface project using the M-V-VM design pattern. I took the naive approach and removed the StartupUri statement in App.xaml and added code below in the code-behind.

protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

var demoViewModel = new DemoViewModel();
var demoView = new DemoView
{
DataContext = demoViewModel
};

MainWindow = demoView;
MainWindow.Show();
}

The idea is to hook up the view model (DemoViewModel) before showing the main view (DemoView.xaml) which is a SurfaceWindow. This worked fine until I hooked up to the Surface Shell on our surface board. Somehow, when I start the application in the Surface Shell the Shell is on top of my application. When I alt-tab I can see my application running in the background. For some reason my main view doesn’t get incorporated into the Shell as it should do.

Anyway, I made a workaround, restored the StartupUri property to “DemoView.xaml” and added some non-M-V-MV code. I’ve posted this problem on the Surface Community for a couple of weeks ago. The Surface support has contacted me on email to this matter. But a simple solution would to not make the main SurfaceWindow to a View (as according to the M-V-VM design pattern) but instead to something like the sample code that Dan Cravier posted on his blog ages ago. I will still update when I get a proper solution from the Surface Support.