The navigation control in WPF, MagicBar, exemplifies a structural and design approach for multi-screen connections through menu configurations, leveraging animations and ListBox technology. This comprehensive guide details the intricacies of its implementation, promoting WPF's flexibility across various platforms while emphasizing design, animation, and code quality improvements.
Introduction
WPF applications traditionally prefer a programmatic approach that connects multiple screens through menu configurations and presents them in a unified manner. This technique, often referred to as the menu or Navigation, is one of the core implementations in WPF. It also has a direct correlation with the architecture (design) of the project, so paying more attention to its implementation can positively impact the quality of the project.
This control features a design and animations specialized for mobile, but it can be elegantly and structurally implemented using ListBox
and Animation
technologies available in WPF. Additionally, it can be similarly implemented in Cross-Platform environments such as AvaloniaUI, Uno, OpenSilver, MAUI, which allows this project to be researched and applied across various platforms.
The goal is also to widely promote the flexibility and excellence of WPF implementation and share the technology. Through this project, we hope you will deeply experience the charm of WPF.
Learning through Tutorial Videos and CodeProject Article
This control comes with a tutorial video, approximately 30 minutes long, available in English and Chinese audio with Korean subtitles. Creating a tutorial video requires more time and effort than one might think, and while it's challenging, your encouragement and support are becoming a great motivation.
You can learn more through the following platforms:
Additionally, check out other tutorial videos like ThemeSwitch, Lol-PlayButton, etc.
Design and Structural Philosophy
This control style is one used widely through web or mobile navigation configurations. Therefore, it's commonly seen implemented using IOS, Android, or HTML/CSS technologies. Implementing it with CSS/HTML and JavaScript allows for relatively easy construction of structure and animation functions. In contrast, WPF, through XAML, might feel comparatively complex in terms of design, event, and animation implementation. Thus, the key to this control's implementation is to make the most of WPF's characteristics and provide a high-level implementation method that lets users feel the structural strengths of WPF.
A lot of focus has been put into the quality of the Source code through Refactoring. The project minimizes/optimizes hierarchical XAML structures and emphasizes enhancing code quality through interaction between XAML and Behind code using CustomControl
. The control isn't just about providing basic functionality; it's about conveying technical inspiration and encouraging diverse applications through its structural philosophy.
Project Overview
MagicBar.cs
MagicBar
, the core control of this project, is a CustomControl
inheriting from ListBox
control. In most development scenarios, UserControl
is the usual choice, but for functions involving complex features, animations, and repetitive elements like in this case, it's more effective to divide and implement them as smaller Control
(CustomControl
) units.
If you're not familiar with CustomControl
, please read the following:
Quote:
The CustomControl
approach itself is technically challenging and conceptually different from traditional desktop methods like Windows Forms, making it somewhat difficult to approach easily. Additionally, finding reference materials for guidance is challenging. However, this is an important process to elevate your WPF technical skills. We encourage you to open-mindedly take on the challenge of CustomControl
implementation with this opportunity.
Generic.xaml
CustomControl
is characterized by its separation and management of the XAML Design area. Therefore, it doesn't provide direct interaction between the XAML area and the control (Class
). Interaction between these two areas is supported through other indirect methods. The first method involves exploring the Template
area through the OnApplyTemplate
timing. The second method extends binding through DependencyProperty
declarations.
This structural feature allows for a perfect separation of design and code, enhancing code reusability and extensibility, and understanding the traditional structure of WPF in depth. All controls used in WPF follow this same method. To verify this, you can directly explore the open-source dotnet/WPF repository available on GitHub.
XAML Configuration
Introduction to Geometry
Geometry is one of the design elements provided in WPF, used for vector-based designs. Traditionally, development methods preferred bitmap images like PNG or JPEG, but there's a growing preference for vector-based designs in recent times. This change can be attributed to improvements in computer performance, developments in monitor resolutions, and shifts in design trends. Hence, the role of Geometry elements is significant in this control. The process of implementing the Circle in the latter part is explained in more detail.
Separation of Animation Elements and ItemsPresenter
MagicBar
inherits from the ListBox
control and uniquely uses the ItemsPresenter
element provided through the ItemsControl
feature. However, interaction between child elements within the ItemsPresenter
is not possible, implying that continuing Animation
actions among child items is also unfeasible.
The behavior of ListBoxItem
is determined by the type of Panel
specified through the ItemsPanelTemplate
in the ItemsPresenter
element. Therefore, the choice of Panel layout significantly affects the behavior of ListBoxItem
. In the case of StackPanel
, the order of the added child elements in the Children
collection determines their position. For Grid
, placement is determined by Row
/Column
settings.
Thus, linking Animation actions between child elements structurally is not possible.
Quote:
However, there are exceptions. In the case of Canvas
, interaction through Animation
is possible using the concept of coordinates, but it requires complex calculations and precise implementation for all controls. Yet, better implementation methods exist, so Canvas
control content is omitted in this context.
ListBox ControlTemplate Hierarchy
Usually, in implementing ListBox
control, greater emphasis is placed on the child element ListBoxItem
. However, for this control, the key feature - the Circle
structure - needs to be positioned outside the area of the ItemsPresenter
element. Therefore, forming a complex Template
in the ListBox
control is crucial.
The hierarchy of the ControlTemplate
is as follows:
Quote:
The following is a simplified representation for clarity and differs from the actual Source code content. The Circle
part can easily be found in the text as "PART_Circle
".
<ControlTemplate TargetType="{x:Type ListBox}">
<Grid>
<Circle/>
<ItemsPresenter/>
</Grid>
</ControlTemplate>
As seen above, the key is to position the ItemsPresenter
and Circle
at the same hierarchical level. This arrangement allows the Circle
element's Animation
range to appear as if freely moving across the ItemsPresenter
's child elements. Moreover, it's essential to place the ItemsPresenter
element in front of the Circle
so that the ListBoxItem
element's icons and text do not visually cover the Circle
.
Having discussed the theory, let's now delve into the actual source code for a detailed comparison.
Quote:
The area with x:Name="PART_Circle" corresponds to the Circle.
<Style TargetType="{x:Type local:MagicBar}">
<Setter Property="ItemContainerStyle" Value="{StaticResource MagicBarItem}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Width" Value="440"/>
<Setter Property="Height" Value="120"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MagicBar}">
<Grid Background="{TemplateBinding Background}">
<Grid.Clip>
<RectangleGeometry Rect="0 0 440 120"/>
</Grid.Clip>
<Border Style="{StaticResource Bar}"/>
<Canvas Margin="20 0 20 0">
<Grid x:Name="PART_Circle" Style="{StaticResource Circle}">
<Path Style="{StaticResource Arc}"/>
<Ellipse Fill="#222222"/>
<Ellipse Fill="CadetBlue" Margin="6"/>
</Grid>
</Canvas>
<ItemsPresenter Margin="20 40 20 0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Columns="5"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
ListBoxItem Template Configuration
Unlike the ListBox
control's Template
, the configuration of the ListBoxItem
is relatively simple. Also, since it's unrelated to the Circle Animation
element, it comprises only the menu item's icon and text.
<Style TargetType="{x:Type ListBoxItem}" x:Key="MagicBarItem">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid Background="{TemplateBinding Background}">
<james:JamesIcon x:Name="icon" Style="{StaticResource Icon}"/>
<TextBlock x:Name="name" Style="{StaticResource Name}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In addition, Animation
that changes the position and color of the icon and text is included. As previously mentioned, no special functionality needs to be implemented in this ListBoxItem
element.
Quote:
JamesIcon
is a control provided through the Jamesnet.Wpf
library available via NuGet, offering various icons. To substitute it, you can either use the Path
control for direct Geometry
design implementation or use images with a transparent (Transparent
) background.
JamesIcon Style
JamesIcon
internally includes a Path
control and provides various DependencyProperty
attributes to allow flexible design definitions from the outside. Key properties include Icon
, Width
, Height
, Fill
, etc.
Quote:
Vector-based Geometry
icons offer consistent designs, which is one way to enhance the quality of the control. Therefore, it's worth examining these differences closely.
<Style TargetType="{x:Type james:JamesIcon}" x:Key="Icon">
<Setter Property="Icon" Value="{TemplateBinding Tag}"/>
<Setter Property="Width" Value="40"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Fill" Value="#44333333"/>
</Style>
RelativeSource Binding
Since the JamesIcon
style is separated from the Template
, it's impossible to use TemplateBinding
Tag binding as shown below:
// Binding method that's not possible</code>
<Setter Property="Icon" Value="{TemplateBinding Tag}"/>
Therefore, RelativeSource
binding is used to search for the ListBoxItem
, the parent element, and bind its Tag
property, as shown below:
<... Value="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}, Path=Tag}"/>
Using RelativeSource
binding, the original TemplateBinding
of the icon defined within the ListBoxItem
area can be individually moved to the JamesIcon
area. This approach allows each component (JamesIcon
) to have its own definition and style, making the code more modular, easier to maintain, and reusable. Separating bindings and styles into their respective areas clarifies the overall code structure, making it easier to understand and modify. Additionally, this separation provides greater flexibility, allowing individual components' styles and behaviors to be adjusted without affecting other components.
2. Microsoft Blend: Geometry Design
Microsoft Blend, the successor to Expression Blend, continues to hold its name despite a reduction in certain features. This program can be added during the installation process of Visual Studio. If you can't find this program, it's possible to add it via the Visual Studio Installer.
Although Microsoft Blend shares most features with Visual Studio, it includes some additional features specialized in design. Among them are functions related to Geometry
, partially resembling features found in Adobe's Illustrator.
Using Microsoft Blend in WPF development isn't essential, nor is it exclusively for designers. Instead, it serves as a valuable tool for developers to create professional and attractive design elements without extensive design training.
Quote:
However, most of the design features provided by Microsoft Blend can be more powerfully utilized in environments like Figma and Illustrator, so there's no pressing need to learn it. But some features related to Geometry are easy to use without separate training, and thus worth examining closely.
Analysis of Circle (🔵) Design
The Circle
in the MagicBar
control is a crucial point of this project, visually functioning as the menu changes. It includes smooth Animation
, adding a contemporary and trendy design element.
The Circle
element doesn't necessarily have to be implemented based on Geometry
. Using an image could be a simpler method. However, in terms of quality, Geometry
designs are becoming more popular as they can handle resolution changes due to size variations more delicately.
Quote:
As shown in the image below, a characteristic of Geometry
is that you can resize it as much as you want without losing clarity.
If you look closely at the Circle
design, you'll see that it creates a sense of space by overlapping a black circle and a green circle. Additionally, rounding the lines on both sides makes it blend naturally into the MagicBar
area. This not only looks visually smooth but also appears more elegant when animated. However, implementing this arc can be challenging and is often abandoned during practical implementation.
But this is where Microsoft Blend becomes useful in easily creating these special shapes.
Drawing Method
The design process involves drawing a large circle with a convex arc at the bottom, then adding smaller circles of the same height on both sides of the large circle. By adjusting the diameter of the large circle, ensure that the large and small circles intersect perfectly.
Next, use the merge
function to cut the unnecessary parts of the large circle and the subtract
function to remove unwanted parts of the small circle, leaving only the arc shape at the intersection. Finally, add a rectangle and remove unnecessary parts to create a unique and natural arc shape.
This method of implementing design elements not only demonstrates how to use Microsoft Blend for complex graphics but also provides a new perspective on thinking and solving design problems. This approach makes the circle not only aesthetically appealing but also technically innovative, enhancing quality.
3. Animation: ListBoxItem
The animation behavior in the ListBoxItem
area, which includes icons and text, is relatively simple. It features moving components upwards and adjusting opacity transparency when IsSelected
is set to true
.
Quote:
Please carefully observe the animation path and effects through the image below:
As shown in the image above, the animation is triggered each time the IsSelected
value of the ListBox
control changes. Additionally, since the movement of the icon and text doesn't go beyond the ListBoxItem
area, it's preferable to implement a static Storyboard
element directly within XAML.
Quote:
This can be controlled using a Trigger
or VisualStateManager
module. For this control, a simple Trigger
module approach is utilized for handling just the IsSelected
action.
Storyboard
For the ListBoxItem
area's animation behavior, it's necessary to prepare scenarios for both when IsSelected
is true
and when it's false
.
<Storyboard x:Key="Selected">
<james:ThickItem Mode="CubicEaseInOut" TargetName="icon"
Duration="0:0:0.5" Property="Margin" To="0 -80 0 0"/>
<james:ThickItem Mode="CubicEaseInOut" TargetName="name"
Duration="0:0:0.5" Property="Margin" To="0 45 0 0"/>
<james:ColorItem Mode="CubicEaseInOut" TargetName="icon"
Duration="0:0:0.5" Property="Fill.Color" To="#333333"/>
<james:ColorItem Mode="CubicEaseInOut" TargetName="name"
Duration="0:0:0.5" Property="Foreground.Color" To="#333333"/>
</Storyboard>
<Storyboard x:Key="UnSelected">
<james:ThickItem Mode="CubicEaseInOut" TargetName="icon"
Duration="0:0:0.5" Property="Margin" To="0 0 0 0"/>
<james:ThickItem Mode="CubicEaseInOut" TargetName="name"
Duration="0:0:0.5" Property="Margin" To="0 60 0 0"/>
<james:ColorItem Mode="CubicEaseInOut" TargetName="icon"
Duration="0:0:0.5" Property="Fill.Color" To="#44333333"/>
<james:ColorItem Mode="CubicEaseInOut" TargetName="name"
Duration="0:0:0.5" Property="Foreground.Color" To="#00000000"/>
</Storyboard>
The key here is specifying the movement path in 'Selected
' and the return path in 'UnSelected
'.
Trigger
Finally, the implementation of animation in the ListBoxItem
area concludes by declaring BeginStoryboard
using Trigger
to activate the respective (Selected
/UnSelected
) Storyboard
s.
Quote:
Unlike typical Trigger
property changes, animations require a return scenario as well.
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource Selected}"/>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource UnSelected}"/>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
The method of configuring animation in the ListBoxItem
area is relatively simple. However, implementing the movement of the Circle
component, which is introduced next, requires more complex calculations for dynamic behavior.
4. Movement of the Circle Component
Now it's time to implement the animation for the movement of the Circle
component. Below is a video showing the dynamic movement of the Circle
.
The movement of the Circle
component must be precisely calculated based on the clicked position, so it can't be implemented in XAML and needs to be handled dynamically in C# code. Therefore, a method for connecting XAML and Code Behind is required.
OnApplyTemplate
This method is used to retrieve the Circle
area inside the MagicBar
control. It's called internally at the connection point between the control and the template. Hence, it's implemented in the MagicBar
class via override.
Then, the 'PART_Circle
' named circle
element is searched using the GetTemplateChild
method. This Grid
will be the target element for displaying the animation effect during interaction.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Grid grid = (Grid)GetTemplateChild("PART_Circle");
InitStoryboard(grid);
}
InitStoryboard
This method initializes the animation. Instances of ValueItem (_vi)
and Storyboard (_sb)
are created first. The animation effect set in ValueItem
is QuinticEaseInOut
, which slows down at the start and end of the animation, making it look smooth and natural.
The movement path for the Circle
is specified as Canvas.LeftProperty
, meaning it changes the horizontal position of the target
element. The duration of the animation is set to 0.5 seconds. Finally, the animation target is set as the Circle
component (Grid
), and the defined animation is added to the storyboard.
private void InitStoryboard(Grid circle)
{
_vi = new();
_sb = new();
_vi.Mode = EasingFunctionBaseMode.QuinticEaseInOut;
_vi.Property = new PropertyPath(Canvas.LeftProperty);
_vi.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 500));
Storyboard.SetTarget(_vi, circle);
Storyboard.SetTargetProperty(_vi, _vi.Property);
_sb.Children.Add(_vi);
}
OnSelectionChanged
The scenario for moving the Circle
component is now implemented. In the MagicBar
class, the OnSelectionChanged
event method is implemented to handle the 'PART_Circle
' (Grid
) element and to execute (Begin
) the storyboard.
Quote:
The MagicBar
control, being a CustomControl
derived from ListBox
, has the advantage of flexibly implementing override features.
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
_vi.To = SelectedIndex * 80;
_sb.Begin();
}
In this method, the logic to dynamically calculate and change the To
value based on the SelectedIndex
is implemented every time the selected menu changes.
5. Conclusion: Examining the Complete Source Code of the CustomControl
Finally, it's time to take a look at the complete structure of the XAML/Csharp code for the MagicBar
control. This is an opportunity to see how elegantly and succinctly the control is implemented within the CustomControl
structure.
Generic.xaml
Despite the implementation of various features, you can observe the maximally streamlined structure of XAML. Notably, the ControlTemplate
structure included in the MagicBar
simplifies complex layer hierarchies for easy viewing. Additionally, even small elements like Storyboard
, Geometry
, TextBlock
, and JamesIcon
are organized in a regular and systematic manner.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:james="https://jamesnet.dev/xaml/presentation"
xmlns:local="clr-namespace:NavigationBar">
<Storyboard x:Key="Selected">
<james:ThickItem Mode="CubicEaseInOut" TargetName="icon"
Duration="0:0:0.5" Property="Margin" To="0 -80 0 0"/>
<james:ThickItem Mode="CubicEaseInOut" TargetName="name"
Duration="0:0:0.5" Property="Margin" To="0 45 0 0"/>
<james:ColorItem Mode="CubicEaseInOut" TargetName="icon"
Duration="0:0:0.5" Property="Fill.Color" To="#333333"/>
<james:ColorItem Mode="CubicEaseInOut" TargetName="name"
Duration="0:0:0.5" Property="Foreground.Color" To="#333333"/>
</Storyboard>
<Storyboard x:Key="UnSelected">
<james:ThickItem Mode="CubicEaseInOut" TargetName="icon"
Duration="0:0:0.5" Property="Margin" To="0 0 0 0"/>
<james:ThickItem Mode="CubicEaseInOut" TargetName="name"
Duration="0:0:0.5" Property="Margin" To="0 60 0 0"/>
<james:ColorItem Mode="CubicEaseInOut" TargetName="icon"
Duration="0:0:0.5" Property="Fill.Color" To="#44333333"/>
<james:ColorItem Mode="CubicEaseInOut" TargetName="name"
Duration="0:0:0.5" Property="Foreground.Color" To="#00000000"/>
</Storyboard>
<Style TargetType="{x:Type james:JamesIcon}" x:Key="Icon">
<Setter Property="Icon"
Value="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},
Path=Tag}"/>
<Setter Property="Width" Value="40"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Fill" Value="#44333333"/>
</Style>
<Style TargetType="{x:Type TextBlock}" x:Key="Name">
<Setter Property="Text"
Value="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem},
Path=Content}"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="#00000000"/>
<Setter Property="Margin" Value="0 60 0 0"/>
</Style>
<Style TargetType="{x:Type ListBoxItem}" x:Key="MagicBarItem">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid Background="{TemplateBinding Background}">
<james:JamesIcon x:Name="icon" Style="{StaticResource Icon}"/>
<TextBlock x:Name="name" Style="{StaticResource Name}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource Selected}"/>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource UnSelected}"/>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Geometry x:Key="ArcData">
M0,0 L100,0 C95.167503,0 91.135628,3.4278221 90.203163,7.9846497 L90.152122,
8.2704506 89.963921,9.1416779 C85.813438,27.384438 69.496498,41 50,41 30.5035,
41 14.186564,27.384438 10.036079,9.1416779 L9.8478823,8.2704926 9.7968359,
7.9846497 C8.8643732,3.4278221 4.8324914,0 0,0 z
</Geometry>
<Style TargetType="{x:Type Path}" x:Key="Arc">
<Setter Property="Data" Value="{StaticResource ArcData}"/>
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Fill" Value="#222222"/>
<Setter Property="Margin" Value="-10 40 -10 -1"/>
</Style>
<Style TargetType="{x:Type Border}" x:Key="Bar">
<Setter Property="Background" Value="#DDDDDD"/>
<Setter Property="Margin" Value="0 40 0 0"/>
<Setter Property="CornerRadius" Value="10"/>
</Style>
<Style TargetType="{x:Type Grid}" x:Key="Circle">
<Setter Property="Width" Value="80"/>
<Setter Property="Height" Value="80"/>
<Setter Property="Canvas.Left" Value="-100"/>
</Style>
<Style TargetType="{x:Type local:MagicBar}">
<Setter Property="ItemContainerStyle" Value="{StaticResource MagicBarItem}"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Width" Value="440"/>
<Setter Property="Height" Value="120"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MagicBar}">
<Grid Background="{TemplateBinding Background}">
<Grid.Clip>
<RectangleGeometry Rect="0 0 440 120"/>
</Grid.Clip>
<Border Style="{StaticResource Bar}"/>
<Canvas Margin="20 0 20 0">
<Grid x:Name="PART_Circle" Style="{StaticResource Circle}">
<Path Style="{StaticResource Arc}"/>
<Ellipse Fill="#222222"/>
<Ellipse Fill="CadetBlue" Margin="6"/>
</Grid>
</Canvas>
<ItemsPresenter Margin="20 40 20 0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Columns="5"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MagicBar.cs
The process of locating the disjointed ControlTemplate
elements through OnApplyTemplate
is a very important and fundamental task, akin to a symbol of WPF. Finding the designated PART_Circle
object (Grid
) and dynamically composing and activating the Circle
's movement (Move
) animation whenever the menu changes serves to vividly demonstrate the vitality and dynamic capabilities of WPF.
using Jamesnet.Wpf.Animation;
using Jamesnet.Wpf.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace NavigationBar
{
public class MagicBar : ListBox
{
private ValueItem _vi;
private Storyboard _sb;
static MagicBar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MagicBar),
new FrameworkPropertyMetadata(typeof(MagicBar)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Grid grid = (Grid)GetTemplateChild("PART_Circle");
InitStoryboard(grid);
}
private void InitStoryboard(Grid circle)
{
_vi = new();
_sb = new();
_vi.Mode = EasingFunctionBaseMode.QuinticEaseInOut;
_vi.Property = new PropertyPath(Canvas.LeftProperty);
_vi.Duration = new Duration(new TimeSpan(0, 0, 0, 0, 500));
Storyboard.SetTarget(_vi, circle);
Storyboard.SetTargetProperty(_vi, _vi.Property);
_sb.Children.Add(_vi);
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
_vi.To = SelectedIndex * 80;
_sb.Begin();
}
}
}
As such, by implementing features that would normally be handled through UserControl
in a CustomControl
approach at the control level, we can achieve more sophisticated and efficient modularization.
With this, I conclude the explanation of the main features. Detailed information about this control is freely available through the GitHub source code. Additionally, in-depth tutorials are provided in both English and Chinese on YouTube and Bilibili, respectively. I look forward to seeing the diverse research and application of this control in XAML-based platforms.
History
- 10th January, 2024: Initial version