Blend

Loading a specific tab when navigating to a page in SketchFlow

A user on the expression forums asked a question about how they could activate a specific tab item on a screen being navigated to. Below is a simple behavior that creates the desired behavior.

This works by assigning a behavior to the click event on the object that triggers the navigation in addition to the navigation behavior. When executed it saves the desired tab name to a static variable that is then read by a behavior attached to TabItems in the target screen. If the text name assigned on each end matches, that tab is activated. The names do not need to correspond with the name of the TabItem or source item in xaml, they can be assigned any string as long as it is identical on each end.

The behavior attached to the TabItems must have its trigger set to loaded so it runs when the screen is displayed.

In the example code below there are 2 buttons that navigate to screen 2, one of the targets the TabItem “One”, and the other “Two.” As I said previously, these names are arbitrary. On screen 2, the TabControl has 2 TabItems, each with a behavior that specifies the matching TabName (“One” or “Two”). These 2 behaviors are set to run on the loaded event, so they are run when the screen is displayed.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
namespace WpfPrototype20Screens
{
	public class SaveTabNameAction : TriggerAction<DependencyObject>
	{
		public static readonly DependencyProperty TargetTabNameProperty = DependencyProperty.Register("TargetTabName", typeof(string), typeof(SaveTabNameAction), new PropertyMetadata("TabName"));

		public string TargetTabName
		{
			get { return (string)GetValue(TargetTabNameProperty); }
			set { SetValue(TargetTabNameProperty, value); }
		}

		protected override void Invoke(object parameter)
		{
			LastTabName = this.TargetTabName;
		}

		public static string LastTabName { get; set; }
	}

	public class ActivateTabOnNavigationAction  : TriggerAction<TabItem>
	{
		public static readonly DependencyProperty TabNameProperty = DependencyProperty.Register("TabName", typeof(string), typeof(ActivateTabOnNavigationAction), new PropertyMetadata("TabName"));

		public string TabName
		{
			get { return (string)GetValue(TabNameProperty); }
			set { SetValue(TabNameProperty, value); }
		}

		protected override void Invoke(object parameter)
		{
			if(!string.IsNullOrWhiteSpace(SaveTabNameAction.LastTabName) && this.TabName == SaveTabNameAction.LastTabName)
			{
				SaveTabNameAction.LastTabName = string.Empty;
				// Try to activate this tab.
				var tc = this.AssociatedObject.Parent as TabControl;
				if (tc == null)
				{
					return;
				}
				tc.SelectedItem = this.AssociatedObject;
			}
		}
	}
}
<UserControl
	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:i="http://schemas.microsoft.com/expression/2010/interactivity" 
	xmlns:pi="http://schemas.microsoft.com/prototyping/2010/interactivity" 
	xmlns:local="clr-namespace:WpfPrototype20Screens" 
	x:Name="Screen_1_Name"
	mc:Ignorable="d"
	x:Class="WpfPrototype20Screens.Screen_1"
	Width="640" Height="480">

	<Grid x:Name="LayoutRoot" Background="White">
		<Button Content="Button" HorizontalAlignment="Left" Height="42" Margin="72,35,0,0" Style="{DynamicResource Button-Sketch}" VerticalAlignment="Top" Width="67">
			<i:Interaction.Triggers>
				<i:EventTrigger EventName="Click">
					<local:SaveTabNameAction TargetTabName="One"/>
					<pi:NavigateToScreenAction TargetScreen="WpfPrototype20Screens.Screen_2"/>
				</i:EventTrigger>
			</i:Interaction.Triggers>
		</Button>
		<Button Content="Button" HorizontalAlignment="Left" Height="38" Margin="72,81,0,0" Style="{DynamicResource Button-Sketch}" VerticalAlignment="Top" Width="67">
			<i:Interaction.Triggers>
				<i:EventTrigger EventName="Click">
					<local:SaveTabNameAction TargetTabName="Two"/>
					<pi:NavigateToScreenAction TargetScreen="WpfPrototype20Screens.Screen_2"/>
				</i:EventTrigger>
			</i:Interaction.Triggers>
		</Button>
	</Grid>
</UserControl>
<UserControl
	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:i="http://schemas.microsoft.com/expression/2010/interactivity" 
	xmlns:local="clr-namespace:WpfPrototype20Screens" 
	x:Name="Screen_2_Name"
	mc:Ignorable="d"
	x:Class="WpfPrototype20Screens.Screen_2"
	Width="640" Height="480">

	<Grid x:Name="LayoutRoot" Background="White">
		<TabControl x:Name="TabControl" Margin="44,28,27,31">
			<TabItem Header="TabItem">
				<i:Interaction.Triggers>
					<i:EventTrigger>
						<local:ActivateTabOnNavigationAction TabName="One"/>
					</i:EventTrigger>
				</i:Interaction.Triggers>
				<Grid Background="#FFE5E5E5"/>
			</TabItem>
			<TabItem Header="TabItem">
				<i:Interaction.Triggers>
					<i:EventTrigger>
						<local:ActivateTabOnNavigationAction TabName="Two"/>
					</i:EventTrigger>
				</i:Interaction.Triggers>
				<Grid Background="#FFE5E5E5"/>
			</TabItem>
		</TabControl>
	</Grid>
</UserControl>

Some Simple Blend How-To Videos

This is just a quick round up of a few short screen captures I’ve done over the past year to demonstrate techniques for users on the Blend/SketchFlow forums, they don’t include audio, but rather just show the steps in a  live recording (with very little planning :)) of me doing the steps on my desktop.


Blend 4: Collaborative SketchFlow Feedback with SharePoint

Check out the new collaborative feedback using SharePoint features in Expression Blend/SketchFlow 4.

http://electricbeach.org/?p=706


Expression Studio 4 Launched!

Today (7-Jun-2010) at Information Week in New York, Microsoft announced the general availability of Expression Studio 4 which includes upgraded versions of Expression Blend (including Sketchflow), Encoder, Web (including SuperPreview) and Design.

You can find out the details of each product and download a trial at http://www.microsoft.com/expression right now.

With this release comes a free Upgrade for licensed version 3 (Studio or Web) users! All you need to do is install the trial version of v4 on top of your licensed version of Expression Studio 3 or Expression Web 3 and the installer will find your license and upgrade it to the full v4 product with no expiration. This applies to customers who received their software through retail channels or electronic software download direct. For customers who have broader license agreements (i.e., MSDN, WebsiteSpark, BizSpark) you should install the product using the software provided from your program site.

For more info:

http://timheuer.com/blog/archive/2010/06/07/expression-studio-4-launch-expression-blend.aspx

Introduction to Expression Studio Ultimate


Action/Behavior to activate tabs within tabs – WPF

This is a WPF version of the previously posted behavior. The main update is to allow either a UserControl or Window as the source of the TabItem.

A user on the Expression forums asked how he could activate a tab within a tab, from a control nested inside those tabs. Normally I would create an action that targeted a TabItem directly, and this could then be used to activate that TabItem using similar code to that below. The problem is that because the TabItem you might be trying to activate may be a sibling to the TabItem your control is in, the system won’t let you target such an item. As an alternative, this behavior takes in the name of a TabItem and activates it and its parents recursively. The code uses reflection to find a reference to the proper TabItem object. This allows you to nest TabItems deeply and activate other tabs from within a tab. The requirement then is that the TabItem have an x:Name applied (rename the item in the Blend Objects and Timeline panel). This name is then specified in the properties of the behavior. The source for the behavior and an example are provided below.

namespace WpfApplication7
{
	using System.Reflection;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Interactivity;

	public class ActivateTabAction : TargetedTriggerAction<FrameworkElement>
	{
		public static readonly DependencyProperty TabNameProperty = DependencyProperty.Register("TabName", typeof(string), typeof(ActivateTabAction), new PropertyMetadata(string.Empty));

		public string TabName
		{
			get { return (string)GetValue(TabNameProperty); }
			set { SetValue(TabNameProperty, value); }
		}

		protected override void Invoke(object o)
		{
			// Find userControl or Window parent.
			var control = this.Target;

			while(control != null && !(control is UserControl) && !(control is Window))
			{
				control = control.Parent as FrameworkElement;
			}

			if(control == null || (!(control is UserControl) && !(control is Window)))
			{
				return;
			}

			// Find the TabItem field by its name.
			var type = control.GetType();
			var target = type.GetField(this.TabName, BindingFlags.IgnoreCase | BindingFlags.NonPublic | BindingFlags.Instance);

			if(target == null || target.FieldType != typeof(TabItem))
			{
				return;
			}

			// Get the tabitem.
			var tabItem = target.GetValue(control) as TabItem;
			if(tabItem == null)
			{
				return;
			}

			ActivateTab(tabItem);

			// Recursively search for tab controls up the visual tree to activate.
			var parent = tabItem.Parent;
			while (parent != null)
			{
				if (parent is TabItem)
				{
					ActivateTab(parent as TabItem);
				}
				if (parent is FrameworkElement)
				{
					parent = (parent as FrameworkElement).Parent;
				}
				else
				{
					parent = null;
				}
			}
		}

		// Find the TabItem's parent TabControl and activate the tab.
		private void ActivateTab(TabItem tabItem)
		{
			var tabControl = tabItem.Parent as TabControl;
			if (tabControl != null)
			{
				tabControl.SelectedItem = tabItem;
			}
		}
	}
}
<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:local="clr-namespace:WpfApplication7"
	x:Class="WpfApplication7.MainWindow"
	x:Name="Window"
	Title="MainWindow"
	Width="640" Height="480">

	<Grid x:Name="LayoutRoot">
		<TabControl Margin="59,74,82,56">
			<TabItem Header="TabItem">
				<Grid Background="#FFE5E5E5">
					<TabControl Margin="0">
						<TabItem x:Name="tabItem" Header="TabItem">
							<Grid Background="#FFE5E5E5"/>
						</TabItem>
						<TabItem x:Name="tabItem1" Header="TabItem">
							<Grid Background="#FFE5E5E5"/>
						</TabItem>
					</TabControl>
				</Grid>
			</TabItem>
			<TabItem Header="TabItem">
				<Grid Background="#FFE5E5E5">
					<Button Content="Button" HorizontalAlignment="Left" Height="56" Margin="32,46.04,0,0" VerticalAlignment="Top" Width="93">
						<i:Interaction.Triggers>
							<i:EventTrigger EventName="Click">
								<local:ActivateTabAction TabName="tabItem"/>
							</i:EventTrigger>
						</i:Interaction.Triggers>
					</Button>
					<Button Content="Button" HorizontalAlignment="Left" Margin="32,123.04,0,112" Width="93">
						<i:Interaction.Triggers>
							<i:EventTrigger EventName="Click">
								<local:ActivateTabAction TabName="tabItem1"/>
							</i:EventTrigger>
						</i:Interaction.Triggers>
					</Button>
				</Grid>
			</TabItem>
		</TabControl>
	</Grid>
</Window>

Action/Behavior to activate tabs within tabs – Silverlight

A user on the Expression forums asked how he could activate a tab within a tab, from a control nested inside those tabs. Normally I would create an action that targeted a TabItem directly, and this could then be used to activate that TabItem using similar code to that below. The problem is that because the TabItem you might be trying to activate may be a sibling to the TabItem your control is in, the system won’t let you target such an item. As an alternative, this behavior takes in the name of a TabItem and activates it and its parents recursively. The code uses reflection to find a reference to the proper TabItem object. This allows you to nest TabItems deeply and activate other tabs from within a tab. The requirement then is that the TabItem have an x:Name applied (rename the item in the Blend Objects and Timeline panel). This name is then specified in the properties of the behavior. The source for the behavior and an example are provided below.

namespace SilverlightApplication10
{
	using System.Reflection;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Interactivity;

	public class ActivateTabAction : TargetedTriggerAction<FrameworkElement>
	{
		public static readonly DependencyProperty TabNameProperty = DependencyProperty.Register("TabName", typeof(string), typeof(ActivateTabAction), new PropertyMetadata(string.Empty));

		public string TabName
		{
			get { return (string)GetValue(TabNameProperty); }
			set { SetValue(TabNameProperty, value); }
		}

		protected override void Invoke(object o)
		{
			// Find userControl parent.
			var control = this.Target;

			while(control != null && !(control is UserControl))
			{
				control = control.Parent as FrameworkElement;
			}

			if(control == null || !(control is UserControl))
			{
				return;
			}

			var uc = control as UserControl;

			// Find the TabItem field by its name.
			var type = uc.GetType();
			var target = type.GetField(this.TabName, BindingFlags.IgnoreCase | BindingFlags.NonPublic | BindingFlags.Instance);

			if(target == null || target.FieldType != typeof(TabItem))
			{
				return;
			}

			// Get the tabitem.
			var tabItem = target.GetValue(uc) as TabItem;
			if(tabItem == null)
			{
				return;
			}

			ActivateTab(tabItem);

			// Recursively search for tab controls up the visual tree to activate.
			var parent = tabItem.Parent;
			while (parent != null)
			{
				if (parent is TabItem)
				{
					ActivateTab(parent as TabItem);
				}
				if (parent is FrameworkElement)
				{
					parent = (parent as FrameworkElement).Parent;
				}
				else
				{
					parent = null;
				}
			}
		}

		// Find the TabItem's parent TabControl and activate the tab.
		private void ActivateTab(TabItem tabItem)
		{
			var tabControl = tabItem.Parent as TabControl;
			if (tabControl != null)
			{
				tabControl.SelectedItem = tabItem;
			}
		}
	}
}
<UserControl
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:local="clr-namespace:SilverlightApplication10"
	x:Class="SilverlightApplication10.MainPage"
	Width="640" Height="480">

	<Grid x:Name="LayoutRoot" Background="White">
		<sdk:TabControl x:Name="tabControl" Margin="64,64,87,103" SelectedIndex="0">
			<sdk:TabItem Header="TabItem">
				<Grid Background="#FFE5E5E5">
					<sdk:TabControl Margin="0">
						<sdk:TabItem x:Name="tabItem" Header="TabItem">
							<Grid Background="#FFE5E5E5"/>
						</sdk:TabItem>
						<sdk:TabItem x:Name="tabItem1" Header="TabItem">
							<Grid Background="#FFE5E5E5"/>
						</sdk:TabItem>
					</sdk:TabControl>
				</Grid>
			</sdk:TabItem>
			<sdk:TabItem Header="TabItem">
				<Grid Background="#FFE5E5E5">
					<Button x:Name="button" Content="Tab 1 in Tab 1 (action on button)" Height="53" Margin="46,63,189,0" VerticalAlignment="Top">
						<i:Interaction.Triggers>
							<i:EventTrigger EventName="Click">
								<local:ActivateTabAction TargetObject="{Binding ElementName=button}" TabName="tabItem"/>
							</i:EventTrigger>
						</i:Interaction.Triggers>
					</Button>
					<Button x:Name="button1" Content="Tab 2 in Tab 1 (action on button)" Margin="46,120,189,103">
						<i:Interaction.Triggers>
							<i:EventTrigger EventName="Click">
								<local:ActivateTabAction TargetObject="{Binding ElementName=button1}" TabName="tabItem1"/>
							</i:EventTrigger>
						</i:Interaction.Triggers>
					</Button>
				</Grid>
			</sdk:TabItem>
		</sdk:TabControl>
	</Grid>
</UserControl>

Simple Validation Behavior for TextBoxes

A user on the Blend/SketchFlow forums wanted to know if there was an easy way to add very basic validation for a form in a prototype. Below is a simple behavior that will validate that a text box is not empty, or if it matches a regex you provide, or both.

If you add this behavior code to your SL application in Blend, and change the namespace to match your project, and then build, this behavior will be available in the asset panel to drag out onto a TextBox. After you create it, you can select the behavior and edit its properties in the property panel. There you can set the regex that is validated, or set whether empty text is allowed.

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

namespace SilverlightApplication9
{
	using System.Text.RegularExpressions;

	public class SimpleValidationBehavior : Behavior<TextBox>
	{
		public static readonly DependencyProperty RegexStringProperty = DependencyProperty.Register("RegexString", typeof(string), typeof(SimpleValidationBehavior), new PropertyMetadata(string.Empty));

		public string RegexString
		{
			get { return GetValue(RegexStringProperty) as string; }
			set { SetValue(RegexStringProperty, value); }
		}

		public static readonly DependencyProperty CanBeEmptyProperty = DependencyProperty.Register("CanBeEmpty", typeof(bool), typeof(SimpleValidationBehavior), new PropertyMetadata(false));

		public bool CanBeEmpty
		{
			get { return (bool)GetValue(CanBeEmptyProperty); }
			set { SetValue(CanBeEmptyProperty, value); }
		}

		protected override void OnAttached()
		{
			base.OnAttached();

			var tb = this.AssociatedObject;
			if (tb != null)
			{
				tb.TextChanged += new TextChangedEventHandler(tb_TextChanged);
			}
			Validate();
		}

		protected override void OnDetaching()
		{
			base.OnDetaching();

			var tb = this.AssociatedObject;
			if (tb != null)
			{
				tb.TextChanged -= new TextChangedEventHandler(tb_TextChanged);
			}
		}
		void tb_TextChanged(object sender, TextChangedEventArgs e)
		{
			// Validate text.
			Validate();
		}

		private void Validate()
		{
			var tb = this.AssociatedObject;
			var text = tb.Text;

			bool emptyValid = this.CanBeEmpty ? true : !string.IsNullOrEmpty(text);
			bool regexValid = string.IsNullOrEmpty(this.RegexString) ? true : Regex.IsMatch(text, this.RegexString);

			bool valid = emptyValid && regexValid;

			if (valid)
			{
				VisualStateManager.GoToState(tb, "Valid", false);
			}
			else
			{
				if (tb.Focus())
				{
					VisualStateManager.GoToState(tb, "InvalidFocused", true);
				}
				else
				{
					VisualStateManager.GoToState(tb, "InvalidUnfocused", true);
				}
			}
		}
	}
}
<TextBox Height="38" Margin="138,78,314,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" >
			<i:Interaction.Behaviors>
				<local:SimpleValidationBehavior RegexString="[0-9][0-9][a-z]"/>
			</i:Interaction.Behaviors>
		</TextBox>

Copyright © 1996-2010 ChuckHays.net. All rights reserved.
Jarrah theme by Templates Next | Powered by WordPress