Well, our objective will be completed in 3 steps:
1. Create a custom behavior i.e. “Drag Notch Behavior“ to mimic drag and expand/close nature of a notification notch.
2. Create a sample user control i.e. “Drag Notch Control” to use this behavior.
3. Create a sample project to use this control.
PREREQUISITE:
BUILDING HELPER CLASSES:
DRAG NOTCH BEHAVIOR:
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
using nishantcop_Behaviors.Utilities;
namespace nishantcop_Behaviors.Behaviors
{
public class DragNotchBehavior : Behavior
{
private FrameworkElement _elementToAnimate;
public event NotchStateChangedHandler NotchStateChanged;
public delegate void NotchStateChangedHandler(object sender, NotchStateChangedEventArgs e);
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectLoaded;
AssociatedObject.ManipulationDelta += AssociatedObjectManipulationDelta;
AssociatedObject.ManipulationCompleted += AssociatedObjectManipulationCompleted;
}
void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
{
_elementToAnimate = AssociatedObject.GetElementToAnimate();
if (!(_elementToAnimate.RenderTransform is CompositeTransform))
{
_elementToAnimate.RenderTransform = new CompositeTransform();
_elementToAnimate.RenderTransformOrigin = new Point(0.5, 0.5);
}
StartPosition = AssociatedObject.GetTranslatePoint();
}
void AssociatedObjectManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
//var dx = e.DeltaManipulation.Translation.X;
var dy = e.DeltaManipulation.Translation.Y;
var currentPosition = _elementToAnimate.GetTranslatePoint();
_elementToAnimate.SetTranslatePoint(currentPosition.X, currentPosition.Y + dy);
}
private void AssociatedObjectManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
// Create a storyboard that will emulate a 'flick'
var currentPosition = _elementToAnimate.GetTranslatePoint();
var velocity = e.FinalVelocities.LinearVelocity;
if (velocity.Y > 0)
{
//var to = new Point(currentPosition.X, 360 + (velocity.Y / BrakeSpeed));
}
var storyboard = new Storyboard { FillBehavior = FillBehavior.HoldEnd };
var to = new Point(currentPosition.X + (velocity.X / BrakeSpeed),
currentPosition.Y + (velocity.Y / BrakeSpeed));
storyboard.AddTranslationAnimation(_elementToAnimate, currentPosition, GetEndPoint(),
new Duration(TimeSpan.FromMilliseconds(500)),
new CubicEase { EasingMode = EasingMode.EaseOut });
storyboard.Begin();
}
protected override void OnDetaching()
{
AssociatedObject.Loaded -= AssociatedObjectLoaded;
AssociatedObject.ManipulationCompleted -= AssociatedObjectManipulationCompleted;
AssociatedObject.ManipulationDelta -= AssociatedObjectManipulationDelta;
base.OnDetaching();
}
private Point GetEndPoint()
{
double midPoint = Math.Abs(StartPosition.Y) - Math.Abs(_elementToAnimate.GetTranslatePoint().Y);
if (midPoint < Math.Abs(StartPosition.Y) / 2)
{
NotchStateChanged(this, new NotchStateChangedEventArgs(false));
return StartPosition;
}
else
{
IsNotchExpanded = true;
NotchStateChanged(this, new NotchStateChangedEventArgs(true));
return EndPosition;
}
}
///
/// This function tries to collapse a notch if it's expanded.
///
public void TryRollBackNotch()
{
var currentPosition = _elementToAnimate.GetTranslatePoint();
if (currentPosition != StartPosition)
{
var storyboard = new Storyboard { FillBehavior = FillBehavior.HoldEnd };
storyboard.AddTranslationAnimation(_elementToAnimate, currentPosition, StartPosition,
new Duration(TimeSpan.FromMilliseconds(500)),
new CubicEase { EasingMode = EasingMode.EaseOut });
storyboard.Begin();
storyboard.Completed += new EventHandler(storyboard_Completed);
}
}
void storyboard_Completed(object sender, EventArgs e)
{
var storyboard = sender as Storyboard;
storyboard.Completed -= storyboard_Completed;
IsNotchExpanded = false;
NotchStateChanged(this, new NotchStateChangedEventArgs(false));
}
#region IsNotchExpandedProperty
public bool IsNotchExpanded
{
get { return (bool)GetValue(IsNotchExpandedProperty); }
set { SetValue(IsNotchExpandedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsNotchExpanded. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsNotchExpandedProperty =
DependencyProperty.Register("IsNotchExpanded", typeof(bool), typeof(DragNotchBehavior), new PropertyMetadata(false, OnIsNotchExpandedPropertyChanged));
private static void OnIsNotchExpandedPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
DragNotchBehavior behavior = source as DragNotchBehavior;
bool time = (bool)e.NewValue;
// Put some update logic here...
}
#endregion
#region BrakeSpeed
public const string BrakeSpeedPropertyName = "BrakeSpeed";
///
/// Describes how fast the element should brake, i.e. come to rest,
/// after a flick. Higher = apply more brake
///
public int BrakeSpeed
{
get { return (int)GetValue(BrakeSpeedProperty); }
set { SetValue(BrakeSpeedProperty, value); }
}
public static readonly DependencyProperty BrakeSpeedProperty = DependencyProperty.Register(
BrakeSpeedPropertyName,
typeof(int),
typeof(DragNotchBehavior),
new PropertyMetadata(10));
#endregion
#region StartPosition
public Point StartPosition
{
get { return (Point)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value); }
}
// Using a DependencyProperty as the backing store for StartPosition. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StartPositionProperty =
DependencyProperty.Register("StartPosition", typeof(Point), typeof(DragNotchBehavior), new PropertyMetadata(new Point(0, 0)));
#endregion
#region End Position
public Point EndPosition
{
get { return (Point)GetValue(EndPositionProperty); }
set { SetValue(EndPositionProperty, value); }
}
// Using a DependencyProperty as the backing store for EndPosition. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EndPositionProperty =
DependencyProperty.Register("EndPosition", typeof(Point), typeof(DragNotchBehavior), new PropertyMetadata(new Point(0, 0)));
#endregion
}
}
- StartPosition: Element starts it’s animation at this point.
- EndPostion: Element ends it’s animation at this point.
- BrakeSpeed: Speed by which animation should come to halt.
- TryRollBackNotch: A Function to force close/Rollback your Notification Notch control.
- NotchStateChanged: An Event to notify the change in status of Notification Notch control i.e. closed or expanded.
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace nishantcop_Behaviors.Utilities
{
public class NotchStateChangedEventArgs : EventArgs
{
private bool _IsNotchExpanded;
public NotchStateChangedEventArgs(bool isNotchExpanded)
{
this._IsNotchExpanded = isNotchExpanded;
//this.exceededPercentage = exceededPercentage;
// not shown - validation for input
}
public bool IsNotchExpanded
{
get { return this._IsNotchExpanded; }
}
}
}
DRAG NOTCH CONTROL
![]()
Code behind for your DragNotch control:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace WPTestBehavior
{
public partial class DragNotch : UserControl
{
public DragNotch()
{
// Required to initialize variables
InitializeComponent();
}
private void Notch_NotchStateChanged(object sender, nishantcop_Behaviors.Utilities.NotchStateChangedEventArgs e)
{
bool b = e.IsNotchExpanded;
}
public void RollBackNotch()
{
Notch.TryRollBackNotch();
}
}
}
In code behind we are handling the NotchStateChanged event from our behavior and a public method (RollBackNotch) is exposed so that you can force your control to collapsed.
Now, your control is ready you can fill in whatever you want inside this grid, for your notification purpose or whatever you may want to call it
USE DRAG NOTCH CONTROL
For using the DragNotch control in last step I’ve created this sample page:
![]()
VIDEO

