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

Getting a database to attach from a My Document folder

Ever want to mount a database from your My Document Folder?  I do … far too often.  In this instance, my SQL Server database MDF file is in: C:\Users\crutkas\Documents\Visual Studio 2008\Projects\Bartender. 

Going into SQL Server Management Studio, and attaching a database will give you this issue.  As you can see, I can’t go in and use anything there.

image 

So what the issue is I have to give SQL Server’s user that is running under access to that folder!  You can do this a few different ways.  Give access to the SQL Server user to read the entire directory tree for your user (c:\Users\[user]\) or just that folder.

So how do you get the user that SQL Server is running under?  Use Computer Management to figure that out and go to the Services module.

image

image

For me, I’m running it under “Network Service”

Now I go back into either c:\Users\[user]\ or the target folder and get that user access.

image

If you did just the target folder, you’ll have to put in the entire file path in the file name of the Attach Database prompt, if you did the root, you now should be able to see everything via the folder tree!

Final tweaks to improve Thumbnailing and now on CodePlex!

In the prior two posts, I talked about creating and fixing the logic for creating video thumbnails / screenshots but it had a few flaws in it.

The first was I didn’t realize I had to close the MediaPlayer object’s stream, this caused memory to balloon upward after multiple plays.  Another problem was opening the same file for the same screenshots could cause issues in the long run at the same time.  To solve these issues, one was an easy fix, the other required a bit of threading knowledge.

To correct this, I’ll first have a Dictionary object to pass in multiple TimeSpans so I can do multiple captures without having the file open multiple times.  I also created used a Mutex that is tied to the video’s Uri.  This will lock the thread until the other processing has been completed.  This will help reduce the memory load footprint in a threaded environment.  You still can get in trouble by opening up some very large video files in a threaded environment

To get access to the tester app, head over to codeplex and source code can be found there too!

public static void CaptureScreen(Uri source, 
	Dictionary<TimeSpan, object> captureList, 
	double scale, CaptureWorkerDelegate finalWorkerPrimary, 
	CaptureWorkerDelegate finalWorkerThumbnail)
{
	var mutexLock = new Mutex(false, source.GetHashCode().ToString());
	mutexLock.WaitOne();
	
	var player = new MediaPlayer { Volume = 0, ScrubbingEnabled = true };

	player.Open(source);
	player.Pause();
	foreach (var pair in captureList)
	{
		var timeSpan = pair.Key;
		var state = pair.Value;

		player.Position = timeSpan;
		Thread.Sleep(1000);

		var width = player.NaturalVideoWidth;
		var height = player.NaturalVideoHeight;

		var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
		var dv = new DrawingVisual();

		using (DrawingContext dc = dv.RenderOpen())
			dc.DrawVideo(player, new Rect(0, 0, width, height));

		rtb.Render(dv);
		var frame = BitmapFrame.Create(rtb).GetCurrentValueAsFrozen();
		if (finalWorkerPrimary != null)
			finalWorkerPrimary(frame as BitmapFrame, state);

		if (scale > 0 && finalWorkerThumbnail != null)
		{
			var thumbnailFrame =
				BitmapFrame.Create(new TransformedBitmap(frame as BitmapSource, new ScaleTransform(scale, scale))).
					GetCurrentValueAsFrozen();
			var encoder = new JpegBitmapEncoder();
			encoder.Frames.Add(thumbnailFrame as BitmapFrame);

			finalWorkerThumbnail(thumbnailFrame as BitmapFrame, state);
		}
	}
	player.Close();
	mutexLock.ReleaseMutex();
}

Improving Thumbnailing Code

From the prior post about getting thumbnails from a video in .Net, it was just prototype code and wasn’t properly abstracted.  Now it is time to fix it.  We’ll create a class called VideoScreenShot.  This class will function in both an asynchronous and synchronous mode.  This still could be improved by queuing up work but this is a nice refactoring.

To get access to the tester app, head over to codeplex and source code can be found there too!

public delegate void CaptureWorkerDelegate(BitmapFrame frame, object state);
public static void CaptureScreenAsync(Uri source, TimeSpan timeSpan, object state, 
	CaptureWorkerDelegate finalWorkerPrimary)
{
	CaptureScreenAsync(source, timeSpan, -1, 
		state, finalWorkerPrimary, null);
}

public static void CaptureScreenAsync(Uri source, TimeSpan timeSpan, double scale, object state, 
	CaptureWorkerDelegate finalWorkerPrimary, CaptureWorkerDelegate finalWorkerThumbnail)
{
	ThreadPool.QueueUserWorkItem(
		delegate { CaptureScreen(source, timeSpan, scale, 
			state, finalWorkerPrimary, finalWorkerThumbnail); });
}

public static void CaptureScreen(Uri source, TimeSpan timeSpan, object state, 
	CaptureWorkerDelegate finalWorkerPrimary)
{
	CaptureScreen(source, timeSpan, -1, state, 
		finalWorkerPrimary, null);
}

public static void CaptureScreen(Uri source, TimeSpan timeSpan, double scale, object state, 
	CaptureWorkerDelegate finalWorkerPrimary, CaptureWorkerDelegate finalWorkerThumbnail)
{
	var player = new MediaPlayer { Volume = 0, ScrubbingEnabled = true };

	player.Open(source);
	player.Pause();
	player.Position = timeSpan;
	Thread.Sleep(1000);

	var width = player.NaturalVideoWidth;
	var height = player.NaturalVideoHeight;

	var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
	var dv = new DrawingVisual();

	using (DrawingContext dc = dv.RenderOpen())
		dc.DrawVideo(player, new Rect(0, 0, width, height));

	rtb.Render(dv);
	var frame = BitmapFrame.Create(rtb).GetCurrentValueAsFrozen();
	if (finalWorkerPrimary != null)
		finalWorkerPrimary(frame as BitmapFrame, state);

	if (scale > 0 && finalWorkerThumbnail != null)
	{
		var thumbnailFrame = BitmapFrame.Create(
			new TransformedBitmap(frame as BitmapSource, 
				new ScaleTransform(scale, scale))).GetCurrentValueAsFrozen();
		var encoder = new JpegBitmapEncoder();
		encoder.Frames.Add(thumbnailFrame as BitmapFrame);

		finalWorkerThumbnail(thumbnailFrame as BitmapFrame, state);
	}
	
	player.Close();
}

In the form’s code behind, here is my code.  This is just a sample of what can be used.

private delegate void setImageDelegate(string controlName, BitmapFrame frame);
private void setImage(string controlName, BitmapFrame frame)
{
	var control = FindName(controlName);
	if (control != null)
		((Image)control).Source = frame;
}

private void makeThumbnails(BitmapFrame frame, object state)
{
	Dispatcher.Invoke(new setImageDelegate(setImage), (string)state, frame);
}

private void makeJpeg(BitmapFrame frame, object state)
{
	var encoder = new JpegBitmapEncoder();
	encoder.Frames.Add(frame);

	string filename = (string)state + ".jpg";
	using (var fs = new FileStream(filename, FileMode.Create))
		encoder.Save(fs);
}

private void Button_Click(object sender, RoutedEventArgs e)
{
	var source = video.Source;

	VideoScreenShot.CaptureScreenAsync(source, TimeSpan.FromSeconds(10), .1, 
		"image0", makeJpeg, makeThumbnails);
	VideoScreenShot.CaptureScreenAsync(source, TimeSpan.FromSeconds(43) + TimeSpan.FromMilliseconds(760), 
		"image1", makeThumbnails);
}

Now it isn’t so much cleaner?