I am developing a WPF component that can be used to create interactive tours for WPF applications. To further improve the API in terms of usability and intuitiveness, the input of other developers is needed.
The following feedback would be helpful:
- Do you see potential to improve naming?
- Do you like that organization of the functionality?
- Do you see essential requirements that are not supported?
NOTE: I know that Code Review requires working code. However, the complete code for that component is too complex. Therefore I just extracted some parts so it is possible to review at least one aspect (API) of the component. Hopefully that's OK.
NOTE2: Meanwhile the library is released and published as open source project on git hub: https://github.com/JanDotNet/ThinkSharp.FeatureTour
Introduction
A tour is an ordered list of steps, the user can pass through. Each step represents a popup that can be placed nearby a UIElement of the WPF application.
Steps may be passive or active. Passive steps contain an explanation and the user has to click next to go to the next step. Active steps require the user to change the state of the application (e.g. select a special value in a combo box, enter a specific text in a text box and so on) to go to the next step.
Popup - the visual representation of a step
Each step is represented by a popup that looks like the following (red elements are not part of the popup):
Header Area / Content Area
- Displays the values of
Step.Header
/Step.Content
(see classStep
below). - Visual appearance of the areas are customizable via data templates.
- Internally, each area is a
ContentControl
. Its Content property is set to the value ofStep.Header
/Step.Content
. The default data template is aTextBlock
.
Close Button
- The [X]-button on top right can be used to close the tour.
Next Button
- The [Next >>]-button on bottom right can be used to go to the next step (Visibility depends on property
Step.ShowNextButton
).
Do it! Button (not visible in figure above)
- The [Do it!]-button is placed left to the next button and can be used to execute custom logic programmatically (e.g. fill a complex form).
Progess (Step N/M)
- Just a read-only information about the tour progress.
API Description
This section describes the API for using the component.
Popup Placement
The location for each step's popup can be defined in XAML using the attached properties ElementID
and Placement
of the class TourHelper
. The properties can be attached to all elements that derive from UIElement
.
For instance, a TabItem
:
<TabItem Header="Variables"
tour:TourHelper.ElementID="Variables"
tour:TourHelper.Placement="BottomCenter">
<!—Content -->
</TabItem>
The Placement
property defines the placement of the popup relative to the UIElement
.
Supported placements:
- Center (popup is displayed without arrows)
- TopLeft, TopCenter, TopRight
- LeftTop, LeftCenter, LeftBottom
- BottomLeft, BottomCenter, BottomRight
- RightTop, RightCenter, RightBottom
Tour Definition
The classes below are used to define the content of a tour.
The Step
class contains all information related to one step.
public class Step
{
public Step(string elementID, object header)
: this(elementID, header, null, elementID)
{}
public Step(string elementID, object header, object content)
: this(elementID, header, content, elementID)
{}
public Step(string elementID, object header, object content, string id)
{
Header = header;
ElementID = elementID;
ID = id;
Content = content;
}
/// <summary>
/// Gets the header object to show in the header area.
/// </summary>
public object Header { get; private set; }
/// <summary>
/// Gets the content to show in main area of the popup.
/// </summary>
public object Content { get; private set; }
/// <summary>
/// Gets the element ID that indicates the visual element where the step is shown.
/// </summary>
/// <remarks>
/// Use attached property <see cref="TourHelper.ElementIDProperty"/> to define a element id
/// for an <see cref="UIElement"/>.
/// </remarks>
public string ElementID { get; private set; }
/// <summary>
/// Gets the ID of the step.
/// </summary>
/// <remarks>
/// If ID is not set; it will be equal to the <see cref="ElementID"/>
/// The ID can be used in <see cref="FeatureTourNavigator.ForStep"/> or <see cref="FeatureTourNavigator.IfCurrentStepEquals"/>
/// to reference the step.
/// </remarks>
public string ID { get; private set; }
/// <summary>
/// Gets or sets a value that determines if next button is shown.
/// If <see cref="ShowNextButton"/> is null (default), the value from <see cref="Tour.ShowNextButtonDefault"/> is used.
/// </summary>
public bool? ShowNextButton { get; set; }
/// <summary>
/// Gets or sets the key of the content data template.
/// </summary>
/// <remarks>
/// A data template with that key must be accessible by <see cref="FrameworkElement.TryFindResource"/>
/// from the <see cref="UIElement"/> with the element id <see cref="ElementID"/>.
/// If <see cref="ContentDataTemplateKey"/> is not set (null) or the template is not available,
/// the content will be displayed in a text block.
/// </remarks>
public string ContentDataTemplateKey { get; set; }
/// <summary>
/// Gets or sets the key of the header data template.
/// </summary>
/// <remarks>
/// A data template with that key must be accessible by <see cref="FrameworkElement.TryFindResource"/>
/// from the <see cref="UIElement"/> with the element id <see cref="ElementID"/>.
/// If <see cref="HeaderDataTemplateKey"/> is not set (null) or the template is not available,
/// the content will be displayed in a text block.
/// </remarks>
public string HeaderDataTemplateKey { get; set; }
}
The Tour
class gets a list of steps and a color scheme. It can be used to start the tour.
public class Tour
{
/// <summary>
/// Gets or sets the name of the tour.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets a value that indicates if the next button for each step is shown or not.
/// The value can be also specified for each step separately using <see cref="Step.ShowNextButton"/>.
/// </summary>
public bool ShowNextButtonDefault { get; set; }
/// <summary>
/// Gets or sets a list of steps.
/// </summary>
public Step[] Steps { get; set; }
/// <summary>
/// Gets or sets the color scheme for the popup.
/// </summary>
public ColorScheme ColorScheme { get; set; }
/// <summary>
/// Starts the tour.
/// </summary>
public void Start() { /* Logic to start the tour */ }
}
The ColorScheme
class defines some colors that are customizable:
public class ColorScheme
{
public ColorScheme(Color background, Color foreground, Color border)
{
Background = background;
Foreground = foreground;
Border = border;
}
public Color Background { get; set; }
public Color Foreground { get; set; }
public Color Border { get; set; }
}
The following example shows how to create and start a tour:
var tour = new Tour()
{
Name = "My Demo Tour",
ShowNextButtonDefault = true,
Steps = new []
{
new Step("Element01", "Header 01", "Content 01", "StepID01"),
new Step("Element02", new HeaderViewModel(), new ContentViewModel(), "StepID02")
{
ContentDataTemplateKey = "ContentTemplate02",
HeaderDataTemplateKey = "HeaderTemplate02"
},
// ...
}
};
tour.Start();
Tour Navigation
The FeatureTourNavigator
class provides some static methods that can be used to control the tour during application logic and to control the application during tour logic.
# Navigate Programmatically
FeatureTourNavigator.IfCurrentStepEquals("StepIDXX").GoNext();
FeatureTourNavigator.IfCurrentStepEquals("StepIDXX").GoPrevious();
FeatureTourNavigator.IfCurrentStepEquals("StepIDXX").Close();
Use Case:
Navigate programmatically for certain steps.
Example:
Text on popup: Create a new item.
To move automatically to the next step if a new item was created, the application logic for creating an item can be extended with:
FeatureTourNavigator.IfCurrentStepEquals("StepIDXX").GoNext();
# Attach doable action (that makes the 'Do it!'-button visible for the specific step)
// Option 01: DoAction only
FeatureTourNavigator.ForStep("StepIDXX").AttachDoable(
currentStep => { /* DoAction - will be executed when the 'Do it!'-button was pressed */ });
// Option 02: DoAction + CanDoAction (for disabling DO-button if needed)
FeatureTourNavigator.ForStep("StepIDXX").AttachDoable(
currentStep => { /* DoAction - will be executed when the 'Do it!'-button was pressed */ },
currentStep => { /* CanDoAction */ return true; });
Use Case:
Enter required input (like complex text) programmatically.
Example:
Text on popup: Enter the text: 'C:\Users\CurrentUser\AppData\Roaming\Company\App\ExampleData\ExampleFile.xml'
The code behind of the window (or also the VM) can be extended with:
FeatureTourNavigator.ForStep("StepIDXX").AttachDoable(currentStep => myTb.Text = "C:\\User...");
# Execute code on step changed / changing
FeatureTourNavigator.OnStepEntered("StepIDXX").Execute(enteredStep => { /* ActionToExecute */ });
FeatureTourNavigator.OnStepEntering("StepIDXX").Execute(enteringStep => { /* ActionToExecute */ });
FeatureTourNavigator.OnStepLeaved("StepIDXX").Execute(leavedStep => { /* ActionToExecute */ });
Use Case:
Create a specific application state programmatically.
Example:
Next step is attached to an element on a specific tab, so it is required that the tab is selected before the tour reaches that step.
The code behind of the window (or also the VM) can be extended with:
FeatureTourNavigator.OnStepEntering("StepIDXX").Execute(enteringStep => { myTabControl.SelectedIndex = 2; });
Tour
class. \$\endgroup\$