Improving a control–Windows Phone 7 Wheel Control

imageThe wheel control, thanks to Rene Schulte for telling me what the term on the street was, is used in a few places in Windows Phone 7 (WP7) and Nick Randolph created a user control to mimic it.  It was great but needed a few little tweaks.  Nick did all the heavy lifting on this.

Currently you can go to his blog entry on it here:  Windows Phone7 Wheel Control – Updated to get the most up to date version.

What I did:

  • Enabled flick gestures
  • Removed a flicker on press
  • On lost focus, snap to grid
  • Allowed for SelectedIndex to be set via XAML

What Nick was doing was levering the ManipulationCompleted event on the scroller in the ListBox and then snapping to grid.  The problem is that when you flick, that event is fired the second your finger is removed from the screen.  The item should snap when it is done moving.  Peter Torr has a blog post on detecting if a list is scrolling or not so I leveraged that bit of code.

I then changed how the CurrentStateChanging event worked in Peter’s code to work with the wheel code.

group.CurrentStateChanging += (s, args) =>
	{
			InvokeCurrentStateChanging(args);
			_currentState = args.NewState;
			if (_currentState.Name == "NotScrolling" && !_userClickedOnItem)
				SnapToGrid();
	};

Now to get this to work properly, there are lots of use cases you have to watch out for.

  • Flicking and click
  • Flicking and lost focus
  • Flicking and it stops scrolling
  • Clicking but not flicking

Fixing the flickering issue fixed and caused a few of the issues.

The root cause of the flickering was how Nick was handling the LostFocus when you would select an item by clicking.  Flicking does not cause this behavior until it runs out of items / stops moving.  LostFocus is fired before SelectionChanged and he would hide everything then show update it.

The MouseEnter event however happens before LostFocus (the magic of breakpoints :-)) so with a simple Boolean to verify that they are clicking and on UpdateFocus, only update if a click event is not in progress.   On MouseLeave and on Scroller_ManipulationCompleted, that boolean, _userClickedOnItem, is set to false.

As is, this control is not currently a Date or Time picker control.  With a bit of work however, it can easily become that.

Coding4Fun T-Shirt Cannons

clip_image002[25]_thumb[1]

Been a tad quiet on the western front and that was due to working on a rather neat project.  I built two robotic t-shirt shooting cannons for the Mix 2010 conference powered by the Windows Phone 7 device.

I’ve started a six-part series on Coding4Fun on how to build one of your own.

WPF, Silverlight, XAML, and Dependency Properties

In my opinion, XAML has a bit of a step learning curve.  I tend to jump head first into something and do my best to learn it.  For some stuff, my head strong, fly-by-the-seat-of-my-pants, style of learning works, XAML was a bit rougher.  I think part of it was I was expected it to work just like HTML which it doesn’t.  I’d pick up a book and play with Expression Blend to help see what does what.  Visual Studio 2010’s updated editor for WPF and Silverlight projects helps a lot as well. 

So why even care about XAML?  This lets you abstract out your views which then lets you be able to update your UI with no repercussions to your data.  Do a Model View ViewModel style of work.  XAML had dependency properties that helps simplify your backend code.  Depending on how something is bound to a field on the page, it will update.  This is really powerful.  The amount of code I didn’t need to write in Drinktendr due to using XAML to manage this really did make a big difference.  With the new Channel9 version coming out the door, Coding4Fun is getting moved over there which means bringing 5 years worth of posts, comments, and source code examples over.  And I plan to do some additional work on the meta data for the posts.  So with modifying BlogML and Meta Weblog To BlogML Converter projects, I created an application to get the data from the blogs.msdn.com platform, get it into blogml as a middle ground data format, then will port it to Channel9’s data format.

So to do this, I created a horrid UI that only I care about.

image

So what is neat here is looking at the code.

from my mainwindow.xaml:

<Controls:LoadAndSave Grid.Column="0" x:Name="loadAndSave"/>
<Controls:ListPosts Grid.Column="1" x:Name="listPosts"
	Posts="{Binding ElementName=loadAndSave, Path=Posts }"/>
<Controls:PostData Grid.Column="3"
	SelectedPost="{Binding ElementName=listPosts, Path=SelectedPost }"/>
<Controls:ListComments Grid.Column="5" 
	Comments="{Binding ElementName=listPosts, Path=SelectedPost.Comments }"/>
<Controls:WebData Grid.Column="7" 
	SelectedPost="{Binding ElementName=listPosts, Path=SelectedPost }"/>

All I’m doing is passing around object references with data binding!  When something gets updated, it is automatically reflected back (* depending on how you have your binding set up).

To create a dependency property, in your code behind, type “propdp” and hit tab twice.  This will be a snippet in Visual Studio that then you can type everything else out.  Here is what the base will look like.

public int MyProperty
{
	get { return (int)GetValue(MyPropertyProperty); }
	set { SetValue(MyPropertyProperty, value); }
}

// Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
	DependencyProperty.Register("MyProperty", typeof(int), typeof(ownerclass), new UIPropertyMetadata(0));

So for the more complex example of me updating the WebData control with the Webbrowser controls, here is my XAML for that control and here is the code behind.  Since WebBrowser uses a method rather than a property to update the content in it, the backend view has a bit more code.

XAML:

<UserControl x:Class="c4fDataPort.Gui.Controls.WebData"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Controls="clr-namespace:c4fDataPort.Gui.Controls">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
			<ColumnDefinition Width="Auto"/> <!-- 1 split -->
			<ColumnDefinition Width="*" />
			<ColumnDefinition Width="Auto"/> <!-- 3 split -->
			<ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
		<GridSplitter Grid.Column="1" Style="{DynamicResource gridSplit}" />
		<GridSplitter Grid.Column="3" Style="{DynamicResource gridSplit}" />
		<Grid Grid.Column="0">
			<Grid.RowDefinitions>
				<RowDefinition Height="{Binding ElementName=menu, Path=Height}" />
				<RowDefinition Height="*" />
			</Grid.RowDefinitions>
			<Label Name="menu" Style="{DynamicResource sectionTitles}">Current Render:</Label>
			<WebBrowser Name="initialWebBrowser"  Grid.Row="1"/>
        </Grid>
		<Grid Grid.Column="2">
			<Grid.RowDefinitions>
				<RowDefinition Height="{Binding ElementName=menu2, Path=Height}" />
				<RowDefinition Height="*" />
			</Grid.RowDefinitions>
			<Label Name="menu2" Style="{DynamicResource sectionTitles}">Edited HTML:</Label>
			<TextBox Grid.Row="1" TextWrapping="WrapWithOverflow" VerticalScrollBarVisibility="Auto"
					 Text="{Binding Path=PostText, RelativeSource={RelativeSource AncestorType={x:Type Controls:WebData}}, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
		</Grid>
		<Grid Grid.Column="4">
			<Grid.RowDefinitions>
				<RowDefinition Height="{Binding ElementName=menu3, Path=Height}" />
				<RowDefinition Height="*" />
			</Grid.RowDefinitions>
			<Label Name="menu3" Style="{DynamicResource sectionTitles}">Final Render:</Label>
			<WebBrowser Name="finalWebBrowser" Grid.Row="1" />
		</Grid>
		
    </Grid>
</UserControl>

CodeBehind:

using System.Windows;
using System.Windows.Controls;

using BlogML.Xml;

namespace c4fDataPort.Gui.Controls
{
	/// <summary>
	/// Interaction logic for WebData.xaml
	/// </summary>
	public partial class WebData : UserControl
	{
		public WebData()
		{
			InitializeComponent();
		}


		private string PostId;
		public string PostText
		{
			get { return (string)GetValue(PostTextProperty); }
			set { SetValue(PostTextProperty, value); }
		}
			
		// Using a DependencyProperty as the backing store for PostText.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty PostTextProperty =
			DependencyProperty.Register("PostText", typeof(string), typeof(WebData), new UIPropertyMetadata("", HtmlChanged));

		public BlogMLPost SelectedPost
		{
			get { return (BlogMLPost)GetValue(SelectedPostProperty); }
			set { SetValue(SelectedPostProperty, value); }
		}

		// Using a DependencyProperty as the backing store for SelectedPost.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty SelectedPostProperty =
			DependencyProperty.Register("SelectedPost", typeof(BlogMLPost), typeof(WebData), new UIPropertyMetadata(new BlogMLPost(), PostChanged));

		private static void PostChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			var typedSender = sender as WebData;
			if (typedSender == null)
				return;

			typedSender.SetInitalPanel();
		}

		private static void HtmlChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
		{
			var typedSender = sender as WebData;
			if (typedSender == null)
				return;

			typedSender.SetFinalPanel();
		}

		private void SetInitalPanel()
		{
			if (SelectedPost.ID == PostId)
				return;

			if (!string.IsNullOrEmpty(SelectedPost.Content.Text))
				initialWebBrowser.NavigateToString(SelectedPost.Content.Text);

			PostText = SelectedPost.Content.Text;
			PostId = SelectedPost.ID;
		}

		private void SetFinalPanel()
		{
			if (!string.IsNullOrEmpty(PostText))
				finalWebBrowser.NavigateToString(PostText);

			SelectedPost.Content.Text = PostText;
		}
	}
}

Then based on certain values, you can do more complex stuff like change the background color or hide stuff.

In this project, if a post is marked “Clean”, the post item is green.  Also based on the post type, additional data will be shown.  Here is how I did that:

XAML:

<UserControl.Resources>
	<Controls:BoolToColorConverter x:Key="backgroundColorConverter" />
</UserControl.Resources>
<StackPanel Background="{Binding Path=SelectedPost.IsDataCleaned, RelativeSource={RelativeSource AncestorType={x:Type Controls:ListPostItem}}, Converter={StaticResource backgroundColorConverter}}" >
	<!-- more stuff -->
</StackPanel>

CodeBehind:

[ValueConversion(typeof(bool), typeof(SolidColorBrush))]
public class BoolToColorConverter : IValueConverter
{
	public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
	{
		return ((bool)value) ? new SolidColorBrush(Color.FromRgb(239, 255, 220)) : new SolidColorBrush(Color.FromRgb(255, 204, 204));
	}

	public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
	{
		throw new NotImplementedException();
	}
}

The more I play with XAML, the more I really do appreciate the power even though there were days were I did hate it.  Also after playing with VS 2010, I do think a lot of my learning issues were solved with additional intellisense features.

Creating more complex buttons in XAML

Ever look at a project and wonder how they got a epic button instead of the every day average one?

There is an extremely easy way to do this in Expression Blend.  I’m also going to show the XAML on how to do it by hand however.

Here is a normal button and an ellipse.

image

XAML:

<Ellipse Fill="White" Stroke="Black" Margin="139,68,0,0" 
   HorizontalAlignment="Left" VerticalAlignment="Top" Width="105" Height="105"/>
<Button Margin="248,68,238,0" Content="Button" 
   VerticalAlignment="Top" Height="105"/>

The button acts like a button but I want the ellipse to act like a button.  In Expression Blend, just right click on the Ellipse (or the most outer container element you want to encapsulate), and go to “Make Into Control”

image

You’ll see a new prompt, click Button, and now you have a full blow button that you can style!

image

What Expression Blend did in the background was the following.

XAML:

<Window.Resources>
	<Style x:Key="ButtonStyle1" TargetType="{x:Type Button}">
		<Setter Property="Template">
			<Setter.Value>
				<ControlTemplate TargetType="{x:Type Button}">
					<Grid>
						<Ellipse Fill="White" Stroke="Black"/>
						<ContentPresenter 
							HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
							VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
							SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
							RecognizesAccessKey="True"/>
					</Grid>
					<ControlTemplate.Triggers>
						<Trigger Property="IsFocused" 
							Value="True"/>
						<Trigger Property="IsDefaulted" 
							Value="True"/>
						<Trigger Property="IsMouseOver" 
							Value="True"/>
						<Trigger Property="IsPressed" 
							Value="True"/>
						<Trigger Property="IsEnabled" 
							Value="False"/>
					</ControlTemplate.Triggers>
				</ControlTemplate>
			</Setter.Value>
		</Setter>
	</Style>
</Window.Resources>

And to reference it in the project, put the style on a button.

XAML:

<Button Style="{DynamicResource ButtonStyle1}" Width="105" Height="105" />

To add content changes like a glow on a mouse over or if it is disabled, add a Setter in the Trigger!

For a quick recap here, to do this by hand, you need the following bit of code.  You can also do this at the Application and User Control level as well.

<Window.Resources>
	<Style x:Key="myStyleName" TargetType="{x:Type Button}">
		<Setter Property="Template">
			<Setter.Value>
				<ControlTemplate TargetType="{x:Type Button}">
					<Grid>
						<!-- your XAML goodness -->
					</Grid>
					<ControlTemplate.Triggers>
						<Trigger Property="IsFocused" 
							Value="True"/>
						<Trigger Property="IsDefaulted" 
							Value="True"/>
						<Trigger Property="IsMouseOver" 
							Value="True"/>
						<Trigger Property="IsPressed" 
							Value="True"/>
						<Trigger Property="IsEnabled" 
							Value="False"/>
					</ControlTemplate.Triggers>
				</ControlTemplate>
			</Setter.Value>
		</Setter>
	</Style>
</Window.Resources>

The button is only clickable when you are on the actual ellipse.  Now we can create more complex things like the back button I created for drinktendr:

image

<Button x:Class="drinktendr.Wpf.Controls.BackButton"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		Style="{DynamicResource arrowBack}" SnapsToDevicePixels="True">
	<Button.Resources>
		<Style x:Key="arrowBack" TargetType="{x:Type Button}">
			<Setter Property="Template">
				<Setter.Value>
					<ControlTemplate TargetType="{x:Type Button}">
						<Grid Background="Black">
							<Viewbox>
								<Grid>
									
									<Path x:Name="arrowTop" Width="262" Height="198" Canvas.Left="45" Canvas.Top="70" 
										Stretch="Fill" StrokeThickness="6" StrokeStartLineCap="Round" StrokeEndLineCap="Round" 
										StrokeLineJoin="Round" Stroke="#FFFF" Fill="#FFF" 
										Data="F1 M 48.5143,170L 144.514,73.9999L 195.514,74L 121.515,150L 304.514,150L 304.514,190L 121.514,190L 195.514,266L 144.514,266L 48.5143,170 Z " RenderTransformOrigin="0.5,0.5">
										<Path.RenderTransform>
											<TransformGroup>
												<ScaleTransform ScaleX="0.7" ScaleY="0.7"/>
												<SkewTransform/>
												<RotateTransform/>
												<TranslateTransform/>
											</TransformGroup>
										</Path.RenderTransform>
									</Path>
									<Path x:Name="arrow" Width="262" Height="198" Canvas.Left="45" Canvas.Top="70" 
										Stretch="Fill" StrokeThickness="6" StrokeStartLineCap="Round" StrokeEndLineCap="Round" 
										StrokeLineJoin="Round" Stroke="#FFFF" Fill="#FFF" 
										Data="F1 M 48.5143,170L 144.514,73.9999L 195.514,74L 121.515,150L 304.514,150L 304.514,190L 121.514,190L 195.514,266L 144.514,266L 48.5143,170 Z " RenderTransformOrigin="0.5,0.5">
										<Path.RenderTransform>
											<TransformGroup>
												<ScaleTransform ScaleX="0.7" ScaleY="0.7"/>
												<SkewTransform/>
												<RotateTransform/>
												<TranslateTransform/>
											</TransformGroup>
										</Path.RenderTransform>
									</Path>
									<Ellipse x:Name="ellipse" Width="340" Height="340" Canvas.Left="0" Canvas.Top="0" Stretch="Fill" StrokeThickness="15" StrokeLineJoin="Round" Opacity=".6" Stroke="#FFFFFF" Fill="#00000000" />
								</Grid>
							</Viewbox>
							<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" RecognizesAccessKey="True"/>
						</Grid>
						<ControlTemplate.Triggers>
							<Trigger Property="IsFocused" Value="True"/>
							<Trigger Property="IsDefaulted" Value="True"/>
							<Trigger Property="IsMouseOver" Value="True">
								<Setter TargetName="ellipse" Property="Opacity" Value="1" />
							</Trigger>
							<Trigger Property="IsPressed" Value="True">
								<Setter TargetName="ellipse" Property="Effect">
									<Setter.Value>
										<DropShadowEffect BlurRadius="50" ShadowDepth="0" RenderingBias="Performance" Color="White" Opacity=".75" />
									</Setter.Value>
								</Setter>
								<Setter TargetName="arrow" Property="Effect">
									<Setter.Value>
										<BlurEffect Radius="30" RenderingBias="Performance" />
									</Setter.Value>
								</Setter>
							</Trigger>
							<Trigger Property="IsEnabled" Value="False"/>
						</ControlTemplate.Triggers>
					</ControlTemplate>
				</Setter.Value>
			</Setter>
		</Style>
	</Button.Resources>
</Button>

Near complete wiring harness

near complete wiring harness

Remember, this use to look like this:

Final wiring for v3 drunktender hardware

Quick mounting to show off look / feel

mounted on acrylic

I have to say, I’m rather happy how these turned out.  This will be mounted between the two legs protected by yet another sheet of acrylic for PDC.  I’m using hex head screws also to mount these since they just look so dang nice.  I had TAP plastics in Seattle do the holes and bends for me.  Chances are there is a plastic place near you that can do this for you.  With the bends and holes, it cost about $40 and they did two of them for me in an hour on a Saturday.  The blue plastic is just protective as I still need to make a few extra holes in them.

Back plane and inductor PCBs soldered up

Small and reduces the rat’s nest of wiring by creating these little puppies.  Everything has a little LED on it to show when it is on too.

back plane PCB
load induction suppression PCB load induction suppression PCB

drinktendr progress

If there is one thing I love, it is getting stuff done.  Here are all the parts for Drinktendr v3.5 and the new PCBs I had created to help aid in wire management.  One is just pure wire management (bottom left) and the other is to help aid in the load induction (bottom right).  The load induction suppressor PCB is designed to directly hook into the valve.

PDC bits  Drinktendr PCBs
Drinktendr PCBs Drinktendr PCBs