WPF

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>

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>

Very Simple Routed Event Example

A user on the Blend/SketchFlow forums asked a question where the answer is to use routed events in WPF.  Here is the trivial example for that question.

The child control that raises the event:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication4
{
	/// <summary>
	/// Interaction logic for ChildControl.xaml
	/// </summary>
	public partial class ChildControl : UserControl
	{
		public ChildControl()
		{
			this.InitializeComponent();
		}

		public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
    		"Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ChildControl));

		// Provide CLR accessors for the event
		public event RoutedEventHandler Tap
		{
	        add { AddHandler(TapEvent, value); }
	        remove { RemoveHandler(TapEvent, value); }
		}

		private void SignalParent(object sender, System.Windows.RoutedEventArgs e)
		{
			this.RaiseEvent(new RoutedEventArgs(TapEvent, this));
		}
	}
}
<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"
	mc:Ignorable="d"
	x:Class="WpfApplication4.ChildControl"
	x:Name="UserControl"
	UseLayoutRounding="True"
	d:DesignWidth="268" d:DesignHeight="143">

	<Grid x:Name="LayoutRoot">
		<Rectangle Fill="Red" Stroke="Black"/>
		<Button Content="Button" HorizontalAlignment="Left" Margin="62,32,0,67" Width="71" Click="SignalParent"/>
	</Grid>
</UserControl>

The parent control that consumes the event:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication4
{
	/// <summary>
	/// Interaction logic for ParentControl.xaml
	/// </summary>
	public partial class ParentControl : UserControl
	{
		public ParentControl()
		{
			this.InitializeComponent();
		}

		private void HandleChildSignal(object sender, System.Windows.RoutedEventArgs e)
		{
			this.Text.Text = "The Child control made this event happen.";
		}
	}
}
<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:local="clr-namespace:WpfApplication4"
	mc:Ignorable="d"
	x:Class="WpfApplication4.ParentControl"
	x:Name="UserControl"
	UseLayoutRounding="True"
	d:DesignWidth="437" d:DesignHeight="325">

	<Grid x:Name="LayoutRoot">
		<Rectangle Fill="Blue" Stroke="Black"/>
		<local:ChildControl Margin="61,87,108,95" Tap="HandleChildSignal"/>
		<TextBlock x:Name="Text" Height="39" Margin="51,19,135,0" TextWrapping="Wrap" Text="This is the original text" VerticalAlignment="Top" Foreground="White" FontSize="24"/>
	</Grid>
</UserControl>

Usage of the parent class:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:local="clr-namespace:WpfApplication4"
	x:Class="WpfApplication4.MainWindow"
	x:Name="Window"
	Title="MainWindow"
	UseLayoutRounding="True"
	Width="640" Height="480">

	<Grid x:Name="LayoutRoot">
		<local:ParentControl Margin="30,33,157,84"/>
	</Grid>
</Window>

WPF Navigation Framework Navigate Behavior

A user on the Expression Blend/SketchFlow forums asked a question about how to navigate between pages in xaml.

There are a number of solutions to this problem, including using a hyperlink item inside an element such as a textblock.

<TextBlock>
    <Hyperlink NavigateUri="Page3.xaml">
        <Run Text="Page 3"/>
    </Hyperlink>
</TextBlock>

However, this will only work when contained within a Page element, and won’t work if you want to have navigation UI in the same page containing a frame. To enable both of these scenarios in an easy to use way, I wrote a simple NavigationAction behavior that will navigate to a Page from either inside a Page, or from an element that is inside the same screen as a Frame element.

The Action created below may be attached to any item inside a page, or any item on a screen that has a Frame object being used to navigate to pages. Set the PageProperty to the filename of the page to be shown.

  • To use this behavior in Blend, add a new Action item to your project, and replicate the code provided below in your action.
  • Build your project.
  • In the asset panel, find your new Action, and drag it onto an element on the artboard.
  • Select the behavior in the Objects and Timeline panel
  • Open the properties panel
  • Set the Page property to the page you want loaded
  • Adjust the trigger event to the event you would like, most likely MouseLeftButtonUp or Down

Source code and example usage are provided below.

The FindChild method is a modified version of code posted here: http://stackoverflow.com/questions/636383/wpf-ways-to-find-controls

	public class NavigateAction : TriggerAction<DependencyObject>
	{
		public static readonly DependencyProperty PageProperty = DependencyProperty.Register("Page", typeof(string), typeof(NavigateAction), new PropertyMetadata(null));

		public string Page
		{
			get { return GetValue(PageProperty) as string; }
			set { SetValue(PageProperty, value); }
		}

		private NavigationService navigationService;
		public NavigateAction()
		{
			NavigationWindow navigationWindow = Application.Current.MainWindow as NavigationWindow;
			if (navigationWindow != null)
			{
				this.navigationService = navigationWindow.NavigationService;
			}

		}

		protected override void Invoke(object o)
		{
			if (this.navigationService == null)
			{
				// Try to find a frame in the main window.
				Frame frame = FindChild<Frame>(Application.Current.MainWindow);
				if (frame != null)
				{
					this.navigationService = frame.NavigationService;
				}
			}

			if (this.navigationService != null)
			{
				this.navigationService.Navigate(new Uri(this.Page, UriKind.Relative));
			}
		}

		public static T FindChild<T>(DependencyObject parent) where T : DependencyObject
		{
			if (parent == null) return null;
			T foundChild = null;
			int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
			for (int i = 0; i < childrenCount; i++)
			{
				var child = VisualTreeHelper.GetChild(parent, i);
				T childType = child as T;
				if (childType == null)
				{
					foundChild = FindChild<T>(child);
					if (foundChild != null)
					{
						return foundChild;
					}
				}
				else
				{
					return (T)child;
				}
			}
			return foundChild;
		}
	}
}

Using the behavior on siblings of a Frame element:

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:WpfApplication21"
	x:Class="WpfApplication21.MainWindow"
	x:Name="Window"
	Title="MainWindow"
	Width="640" Height="480">
	<Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="0.925*"/>
			<RowDefinition Height="0.075*"/>
		</Grid.RowDefinitions>
		<Frame x:Name="MainFrame" Content="" Source="Pages\Page1.xaml" Margin="0,0,0,4"/>
		<StackPanel Grid.Row="1" Orientation="Horizontal">

			<Ellipse Fill="Red" Stroke="Black" Width="100">
				<i:Interaction.Triggers>
					<i:EventTrigger EventName="MouseLeftButtonUp">
						<local:NavigateAction Page="Pages/Page1.xaml"/>
					</i:EventTrigger>
				</i:Interaction.Triggers>
			</Ellipse>
			<Ellipse Fill="#FF00FF1C" Stroke="Black" Width="100">
				<i:Interaction.Triggers>
					<i:EventTrigger EventName="MouseLeftButtonUp">
						<local:NavigateAction Page="Pages/Page2.xaml"/>
					</i:EventTrigger>
				</i:Interaction.Triggers>
			</Ellipse>
			<Ellipse Fill="#FF0BC6CD" Stroke="Black" Width="100">
			<i:Interaction.Triggers>
					<i:EventTrigger EventName="MouseLeftButtonUp">
						<local:NavigateAction Page="Pages/Page3.xaml"/>
					</i:EventTrigger>
				</i:Interaction.Triggers>
			</Ellipse>
		</StackPanel>
	</Grid>
</Window>

Using the behavior on items within a page:

<Page
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:WpfApplication21"
	x:Class="WpfApplication21.Page1"
	x:Name="Page"
	WindowTitle="Page"
	FlowDirection="LeftToRight"
	Width="640" Height="480"
	WindowWidth="640" WindowHeight="480">

	<Grid x:Name="LayoutRoot">
		<StackPanel>
			<TextBlock Text="Page 1" TextWrapping="Wrap">
				<i:Interaction.Triggers>
					<i:EventTrigger EventName="MouseLeftButtonUp">
						<local:NavigateAction Page="Pages/Page1.xaml"/>
					</i:EventTrigger>
				</i:Interaction.Triggers>
			</TextBlock>
			<TextBlock Text="Page 2" TextWrapping="Wrap">
				<i:Interaction.Triggers>
					<i:EventTrigger EventName="MouseLeftButtonUp">
						<local:NavigateAction Page="Pages/Page2.xaml"/>
					</i:EventTrigger>
				</i:Interaction.Triggers>
			</TextBlock>
			<TextBlock HorizontalAlignment="Left" Text="Page 3" TextWrapping="Wrap">
				<i:Interaction.Triggers>
					<i:EventTrigger EventName="MouseLeftButtonUp">
						<local:NavigateAction Page="Pages/Page3.xaml"/>
					</i:EventTrigger>
				</i:Interaction.Triggers>
			</TextBlock>
		</StackPanel>
	</Grid>
</Page>

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