diff --git a/KeyboardAudioVisualizer/Attached/SliderValue.cs b/KeyboardAudioVisualizer/Attached/SliderValue.cs
new file mode 100644
index 0000000..3de7ffd
--- /dev/null
+++ b/KeyboardAudioVisualizer/Attached/SliderValue.cs
@@ -0,0 +1,111 @@
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace KeyboardAudioVisualizer.Attached
+{
+ public static class SliderValue
+ {
+ #region Properties & Fields
+ // ReSharper disable InconsistentNaming
+
+ public static readonly DependencyProperty UnitProperty = DependencyProperty.RegisterAttached(
+ "Unit", typeof(string), typeof(SliderValue), new PropertyMetadata(default(string)));
+
+ public static void SetUnit(DependencyObject element, string value) => element.SetValue(UnitProperty, value);
+ public static string GetUnit(DependencyObject element) => (string)element.GetValue(UnitProperty);
+
+ public static readonly DependencyProperty IsShownProperty = DependencyProperty.RegisterAttached(
+ "IsShown", typeof(bool), typeof(SliderValue), new PropertyMetadata(default(bool), IsShownChanged));
+
+ public static void SetIsShown(DependencyObject element, bool value) => element.SetValue(IsShownProperty, value);
+ public static bool GetIsShown(DependencyObject element) => (bool)element.GetValue(IsShownProperty);
+
+ public static readonly DependencyProperty BorderBrushProperty = DependencyProperty.RegisterAttached(
+ "BorderBrush", typeof(Brush), typeof(SliderValue), new PropertyMetadata(default(Brush)));
+
+ public static void SetBorderBrush(DependencyObject element, Brush value) => element.SetValue(BorderBrushProperty, value);
+ public static Brush GetBorderBrush(DependencyObject element) => (Brush)element.GetValue(BorderBrushProperty);
+
+ public static readonly DependencyProperty BackgroundProperty = DependencyProperty.RegisterAttached(
+ "Background", typeof(Brush), typeof(SliderValue), new PropertyMetadata(default(Brush)));
+
+ public static void SetBackground(DependencyObject element, Brush value) => element.SetValue(BackgroundProperty, value);
+ public static Brush GetBackground(DependencyObject element) => (Brush)element.GetValue(BackgroundProperty);
+
+ public static readonly DependencyProperty ForegroundProperty = DependencyProperty.RegisterAttached(
+ "Foreground", typeof(Brush), typeof(SliderValue), new PropertyMetadata(default(Brush)));
+
+ public static void SetForeground(DependencyObject element, Brush value) => element.SetValue(ForegroundProperty, value);
+ public static Brush GetForeground(DependencyObject element) => (Brush)element.GetValue(ForegroundProperty);
+
+ public static readonly DependencyProperty FontProperty = DependencyProperty.RegisterAttached(
+ "Font", typeof(FontFamily), typeof(SliderValue), new PropertyMetadata(default(FontFamily)));
+
+ public static void SetFont(DependencyObject element, FontFamily value) => element.SetValue(FontProperty, value);
+ public static FontFamily GetFont(DependencyObject element) => (FontFamily)element.GetValue(FontProperty);
+
+ public static readonly DependencyProperty FontSizeProperty = DependencyProperty.RegisterAttached(
+ "FontSize", typeof(double), typeof(SliderValue), new PropertyMetadata(default(double)));
+
+ public static void SetFontSize(DependencyObject element, double value) => element.SetValue(FontSizeProperty, value);
+ public static double GetFontSize(DependencyObject element) => (double)element.GetValue(FontSizeProperty);
+
+ // ReSharper enable InconsistentNaming
+ #endregion
+
+ #region Methods
+
+ private static void IsShownChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
+ {
+ if (!(dependencyObject is Slider slider)) return;
+
+ if (dependencyPropertyChangedEventArgs.NewValue as bool? == true)
+ {
+ slider.MouseEnter += SliderOnMouseEnter;
+ slider.MouseLeave += SliderOnMouseLeave;
+ }
+ else
+ {
+ slider.MouseEnter -= SliderOnMouseEnter;
+ slider.MouseLeave -= SliderOnMouseLeave;
+ RemoveAdorner(slider);
+ }
+ }
+
+ private static void SliderOnMouseEnter(object sender, MouseEventArgs mouseEventArgs)
+ {
+ if (!(sender is Slider slider)) return;
+ AdornerLayer.GetAdornerLayer(slider)?.Add(new SliderValueAdorner(slider, GetUnit(slider))
+ {
+ BorderBrush = GetBorderBrush(slider),
+ Background = GetBackground(slider),
+ Foreground = GetForeground(slider),
+ Font = GetFont(slider),
+ FontSize = GetFontSize(slider)
+ });
+ }
+
+ private static void SliderOnMouseLeave(object sender, MouseEventArgs mouseEventArgs)
+ {
+ if (!(sender is Slider slider)) return;
+ RemoveAdorner(slider);
+ }
+
+ private static void RemoveAdorner(Slider slider)
+ {
+ AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(slider);
+ Adorner adorner = adornerLayer?.GetAdorners(slider)?.FirstOrDefault(x => x is SliderValueAdorner);
+ if (adorner != null)
+ {
+ adornerLayer.Remove(adorner);
+ (adorner as SliderValueAdorner)?.Cleanup();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/KeyboardAudioVisualizer/Attached/SliderValueAdorner.cs b/KeyboardAudioVisualizer/Attached/SliderValueAdorner.cs
new file mode 100644
index 0000000..9f9a235
--- /dev/null
+++ b/KeyboardAudioVisualizer/Attached/SliderValueAdorner.cs
@@ -0,0 +1,99 @@
+using System.Globalization;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+using Point = System.Windows.Point;
+
+namespace KeyboardAudioVisualizer.Attached
+{
+ public class SliderValueAdorner : System.Windows.Documents.Adorner
+ {
+ #region Properties & Fields
+
+ private readonly string _unit;
+ private readonly Slider _slider;
+ private readonly Thumb _thumb;
+ private readonly RepeatButton _decreaseRepeatButton;
+
+ public Brush BorderBrush { get; set; } = Brushes.Black;
+ public Brush Background { get; set; } = Brushes.Black;
+ public Brush Foreground { get; set; } = Brushes.White;
+ public FontFamily Font { get; set; } = new FontFamily("Verdana");
+ public double FontSize { get; set; } = 14;
+
+ #endregion
+
+ #region Constructors
+
+ public SliderValueAdorner(UIElement adornedElement, string unit)
+ : base(adornedElement)
+ {
+ this._unit = unit;
+
+ _slider = (Slider)adornedElement;
+ Track track = (Track)_slider.Template.FindName("PART_Track", _slider);
+
+ _thumb = track.Thumb;
+ _decreaseRepeatButton = track.DecreaseRepeatButton;
+ _decreaseRepeatButton.SizeChanged += OnButtonSizeChanged;
+ }
+
+ #endregion
+
+ #region Methods
+
+ public void Cleanup()
+ {
+ _decreaseRepeatButton.SizeChanged -= OnButtonSizeChanged;
+ }
+
+ private void OnButtonSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs) => InvalidateVisual();
+
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ double offset = _decreaseRepeatButton.ActualWidth + (_thumb.ActualWidth / 2.0);
+
+ FormattedText text = new FormattedText(GetText(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(Font, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), FontSize, Foreground);
+ Geometry border = CreateBorder(offset, text.Width, text.Height);
+
+ drawingContext.DrawGeometry(Background, new Pen(BorderBrush, 1), border);
+ drawingContext.DrawText(text, new Point(offset - (text.Width / 2.0), -26));
+ }
+
+ private string GetText()
+ {
+ string valueText = _slider.Value.ToString();
+ if (!string.IsNullOrWhiteSpace(_unit))
+ valueText += " " + _unit;
+
+ return valueText;
+ }
+
+ private Geometry CreateBorder(double offset, double width, double height)
+ {
+ double halfWidth = width / 2.0;
+
+ PathGeometry borderGeometry = new PathGeometry();
+ PathFigure border = new PathFigure
+ {
+ StartPoint = new Point(offset, 0),
+ IsClosed = true,
+ IsFilled = true
+ };
+
+ border.Segments.Add(new LineSegment(new Point(offset + 4, -6), true));
+ border.Segments.Add(new LineSegment(new Point(offset + 4 + halfWidth, -6), true));
+ border.Segments.Add(new LineSegment(new Point(offset + 4 + halfWidth, -10 - height), true));
+ border.Segments.Add(new LineSegment(new Point(offset - 4 - halfWidth, -10 - height), true));
+ border.Segments.Add(new LineSegment(new Point(offset - 4 - halfWidth, -6), true));
+ border.Segments.Add(new LineSegment(new Point(offset - 4, -6), true));
+
+ borderGeometry.Figures.Add(border);
+
+ return borderGeometry;
+ }
+
+ #endregion
+ }
+}
diff --git a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj
index b36f3ff..fdaf5c8 100644
--- a/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj
+++ b/KeyboardAudioVisualizer/KeyboardAudioVisualizer.csproj
@@ -108,6 +108,8 @@
MSBuild:Compile
Designer
+
+
App.xaml
Code
diff --git a/KeyboardAudioVisualizer/Styles/Slider.xaml b/KeyboardAudioVisualizer/Styles/Slider.xaml
index d686eb8..d6028df 100644
--- a/KeyboardAudioVisualizer/Styles/Slider.xaml
+++ b/KeyboardAudioVisualizer/Styles/Slider.xaml
@@ -1,15 +1,24 @@
+ xmlns:styles="clr-namespace:KeyboardAudioVisualizer.Styles"
+ xmlns:attached="clr-namespace:KeyboardAudioVisualizer.Attached">
-