Tag: Behaviors

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>

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>

Basic Quiz Application Using Behaviors

A user on the Expression Blend/SketchFlow forums recently asked a question about how to create a Quiz type of application in Silverlight without using much coding.  I created a simple example using a simple behavior to store the score of the quiz. Some of this behavior is redundant with new behaviors included with Blend 4, but the example should be illustrative if nothing else.

The behavior works by keeping static state in the ScoreKeeperAction class, which can be used to display the score. Each instance of a QuizQuestionBehavior that is attached to a RadioButton (the correct answer) will register its results with the ScoreKeeperAction class when invoked by determining if the RadioButton it is attached to is checked.

Each instance of the ScoreKeeperAction subscribes to changed events on the score, and updates its text whenever the score changes.

To use the behaviors, create a group of radiobuttons, and attach a QuizQuestionBehavior to the correct answer. Set the action’s trigger to a button that will submit the answer and the event to clicked. Also set the name of the Question in the behavior properties. An example of usage:

<Grid x:Name="LayoutRoot" >
		<RadioButton x:Name="CorrectAnswer" HorizontalAlignment="Left" Margin="45,47,0,0" VerticalAlignment="Top" Width="99" Content="Answer 1 (C)" GroupName="QuizGroup">
			<i:Interaction.Triggers>
				<i:EventTrigger SourceName="button" EventName="Click">
					<local:QuizQuestionBehavior QuestionName="Question 1"/>
				</i:EventTrigger>
			</i:Interaction.Triggers>
		</RadioButton>
        <RadioButton HorizontalAlignment="Left" Margin="45,68,0,0" VerticalAlignment="Top" Content="Answer 2" Width="99" GroupName="QuizGroup"/>
        <RadioButton HorizontalAlignment="Left" Margin="45,89,0,0" VerticalAlignment="Top" Content="Answer 3" Width="99" GroupName="QuizGroup"/>
		<RadioButton HorizontalAlignment="Left" Margin="45,110,0,0" VerticalAlignment="Top" Content="Answer 4" Width="99" GroupName="QuizGroup"/>
		<Button x:Name="button" Height="36" HorizontalAlignment="Left" Margin="45,135,0,0" VerticalAlignment="Top" Width="99" Content="Record Answer"/>
	</Grid>

To display the score, create a TextBlock and attach a ScoreKeeperAction, the user can set the text to be displayed in the behavior properties, SCORE is replaced with the number of correct answers and TOTAL is replaced with the total number of questions answered.

<TextBlock x:Name="ScoreBlock" Margin="9,9,8,8" Grid.Row="1" TextWrapping="Wrap">
	<i:Interaction.Triggers>
		<i:EventTrigger>
			<local:ScoreKeeperAction/>
		</i:EventTrigger>
	</i:Interaction.Triggers>
</TextBlock>

To display a summary create a TextBlock and attach a ScoreKeeperAction as well as setting the SummaryBox checkbox to true in the properties.

<TextBlock x:Name="textBlock" Margin="8,8,23,17" TextWrapping="Wrap" Text="Click update.">
	<i:Interaction.Triggers>
		<i:EventTrigger SourceName="textBlock">
			<local:ScoreKeeperAction SummaryBox="True"/>
		</i:EventTrigger>
	</i:Interaction.Triggers>
</TextBlock>

The code of the 2 Behaviors/Actions:

public class QuizQuestionBehavior : TriggerAction<RadioButton>
	{
		public static readonly DependencyProperty QuestionNameProperty = DependencyProperty.Register("QuestionName", typeof(string), typeof(QuizQuestionBehavior), new PropertyMetadata(Guid.NewGuid().ToString()));

    	public string QuestionName
        {
            get { return GetValue(QuestionNameProperty) as string; }
			set { SetValue(QuestionNameProperty, value); }
        }

		public QuizQuestionBehavior() {	}

		private bool answered = false;
		protected override void Invoke(object o)
		{
			if(!answered)
			{
				if(this.AssociatedObject.IsChecked == (bool?)true)
				{
					// Right
					ScoreKeeperAction.QuestionAnswered(this.QuestionName, true);
				}
				else
				{
					// Wrong
					ScoreKeeperAction.QuestionAnswered(this.QuestionName, false);
				}
			}
			answered = true;
		}
	}
public class ScoreKeeperAction : TriggerAction<TextBlock>
	{
		public static readonly DependencyProperty DisplayTextProperty = DependencyProperty.Register("DisplayText", typeof(string), typeof(ScoreKeeperAction), new PropertyMetadata("Answered SCORE/TOTAL questions correctly."));

		public string DisplayText
		{
			get { return GetValue(DisplayTextProperty) as string; }
			set { SetValue(DisplayTextProperty, value); }
		}

		public static readonly DependencyProperty SummaryBoxProperty = DependencyProperty.Register("SummaryBox", typeof(bool), typeof(ScoreKeeperAction), new PropertyMetadata(false));

		public bool SummaryBox
		{
			get { return (bool)GetValue(SummaryBoxProperty); }
			set { SetValue(SummaryBoxProperty, value); }
		}

		private static int questionsAnswered = 0;
		private static int QuestionsAnswered
		{
			get
			{
				return questionsAnswered;
			}
			set
			{
				if (questionsAnswered != value)
				{
					questionsAnswered = value;
					if (QuestionsAnsweredChanged != null)
					{
						QuestionsAnsweredChanged(null, new EventArgs());
					}
				}
			}
		}

		private static int correctQuestionsAnswered = 0;
		private static int CorrectQuestionsAnswered
		{
			get
			{
				return correctQuestionsAnswered;
			}
			set
			{
				if (correctQuestionsAnswered != value)
				{
					correctQuestionsAnswered = value;
					if (QuestionsAnsweredChanged != null)
					{
						QuestionsAnsweredChanged(null, new EventArgs());
					}
				}
			}
		}

		private static Dictionary<string, bool> questionResults = new Dictionary<string, bool>();

		public static void QuestionAnswered(string questionName, bool correct)
		{
			if (!questionResults.ContainsKey(questionName))
			{
				questionResults[questionName] = correct;
				QuestionsAnswered++;
				if (correct)
				{
					CorrectQuestionsAnswered++;
				}
			}
		}

		public static event EventHandler QuestionsAnsweredChanged;

		public ScoreKeeperAction()
		{
			QuestionsAnsweredChanged += new EventHandler(UpdateDisplay);
			UpdateDisplay();
		}

		private void UpdateDisplay(object sender, EventArgs e)
		{
			UpdateDisplay();
		}

		private void UpdateDisplay()
		{
			string text = this.DisplayText;
			if (String.IsNullOrEmpty(text))
			{
				text = "SCORE/TOTAL";
			}
			text = text.Replace("SCORE", CorrectQuestionsAnswered.ToString());
			text = text.Replace("TOTAL", QuestionsAnswered.ToString());
			if (this.SummaryBox)
			{
				// Add in summary below text.
				var sb = new StringBuilder();
				sb.AppendLine(text);
				foreach (var item in questionResults)
				{
					sb.AppendLine("Question " + item.Key + " answered " + (item.Value ? "correctly" : "incorrectly") + ".");
				}
				text = sb.ToString();
			}

			if (this.AssociatedObject != null)
			{
				this.AssociatedObject.Text = text;
			}
		}

		protected override void Invoke(object o)
		{
			UpdateDisplay();
		}
	}

The complete solution is available here: QuizApp.zip


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