Files
aistudio-wpf-diagram/Live-Charts-master/UwpView/Axis.cs
2021-07-23 09:42:22 +08:00

790 lines
30 KiB
C#

//copyright(c) 2016 Alberto Rodriguez
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Shapes;
using LiveCharts.Charts;
using LiveCharts.Definitions.Charts;
using LiveCharts.Dtos;
using LiveCharts.Events;
using LiveCharts.Uwp.Components;
namespace LiveCharts.Uwp
{
/// <summary>
/// An Axis of a chart
/// </summary>
public class Axis : FrameworkElement, IAxisView
{
#region Constructors
/// <summary>
/// Initializes a new instance of Axis class
/// </summary>
public Axis()
{
TitleBlock = BindATextBlock();
this.SetIfNotSet(SeparatorProperty, new Separator());
this.SetIfNotSet(SectionsProperty, new SectionsCollection());
TitleBlock.SetBinding(TextBlock.TextProperty,
new Binding {Path = new PropertyPath("Title"), Source = this});
}
#endregion
#region Events
/// <summary>
/// Occurs when an axis range changes by an user action (zooming or panning)
/// </summary>
public event RangeChangedHandler RangeChanged;
/// <summary>
/// The range changed command property
/// </summary>
public static readonly DependencyProperty RangeChangedCommandProperty = DependencyProperty.Register(
"RangeChangedCommand", typeof(ICommand), typeof(Axis), new PropertyMetadata(default(ICommand)));
/// <summary>
/// Gets or sets the command to execute when an axis range changes by an user action (zooming or panning)
/// </summary>
/// <value>
/// The range changed command.
/// </value>
public ICommand RangeChangedCommand
{
get { return (ICommand)GetValue(RangeChangedCommandProperty); }
set { SetValue(RangeChangedCommandProperty, value); }
}
/// <summary>
/// Occurs before an axis range changes by an user action (zooming or panning)
/// </summary>
public event PreviewRangeChangedHandler PreviewRangeChanged;
/// <summary>
/// The preview range changed command property
/// </summary>
public static readonly DependencyProperty PreviewRangeChangedCommandProperty = DependencyProperty.Register(
"PreviewRangeChangedCommand", typeof(ICommand), typeof(Axis), new PropertyMetadata(default(ICommand)));
/// <summary>
/// Gets or sets the command to execute before an axis range changes by an user action (zooming or panning)
/// </summary>
/// <value>
/// The preview range changed command.
/// </value>
public ICommand PreviewRangeChangedCommand
{
get { return (ICommand)GetValue(PreviewRangeChangedCommandProperty); }
set { SetValue(PreviewRangeChangedCommandProperty, value); }
}
#endregion
#region properties
private TextBlock TitleBlock { get; set; }
/// <summary>
/// Gets the Model of the axis, the model is used a DTO to communicate with the core of the library.
/// </summary>
public AxisCore Model { get; set; }
/// <summary>
/// Gets previous Max Value
/// </summary>
public double PreviousMaxValue { get; internal set; }
/// <summary>
/// Gets previous Min Value
/// </summary>
public double PreviousMinValue { get; internal set; }
#endregion
#region Dependency Properties
/// <summary>
/// The labels property
/// </summary>
public static readonly DependencyProperty LabelsProperty = DependencyProperty.Register(
"Labels", typeof (IList<string>), typeof (Axis),
new PropertyMetadata(default(IList<string>), UpdateChart()));
/// <summary>
/// Gets or sets axis labels, labels property stores the array to map for each index and value, for example if axis value is 0 then label will be labels[0], when value 1 then labels[1], value 2 then labels[2], ..., value n labels[n], use this property instead of a formatter when there is no conversion between value and label for example names, if you are plotting sales vs salesman name.
/// </summary>
//[TypeConverter(typeof(StringCollectionConverter))]
public IList<string> Labels
{
get { return (IList<string>) GetValue(LabelsProperty); }
set { SetValue(LabelsProperty, value); }
}
/// <summary>
/// The sections property
/// </summary>
public static readonly DependencyProperty SectionsProperty = DependencyProperty.Register(
"Sections", typeof (SectionsCollection), typeof (Axis), new PropertyMetadata(default(SectionsCollection)));
/// <summary>
/// Gets or sets the axis sectionsCollection, a section is useful to highlight ranges or values in a chart.
/// </summary>
public SectionsCollection Sections
{
get { return (SectionsCollection) GetValue(SectionsProperty); }
set { SetValue(SectionsProperty, value); }
}
/// <summary>
/// The label formatter property
/// </summary>
public static readonly DependencyProperty LabelFormatterProperty = DependencyProperty.Register(
"LabelFormatter", typeof (Func<double, string>), typeof (Axis),
new PropertyMetadata(default(Func<double, string>), UpdateChart()));
/// <summary>
/// Gets or sets the function to convert a value to label, for example when you need to display your chart as currency ($1.00) or as degrees (10°), if Labels property is not null then formatter is ignored, and label will be pulled from Labels prop.
/// </summary>
public Func<double, string> LabelFormatter
{
get { return (Func<double, string>) GetValue(LabelFormatterProperty); }
set { SetValue(LabelFormatterProperty, value); }
}
/// <summary>
/// The separator property
/// </summary>
public static readonly DependencyProperty SeparatorProperty = DependencyProperty.Register(
"Separator", typeof (Separator), typeof (Axis),
new PropertyMetadata(default(Separator), UpdateChart()));
/// <summary>
/// Get or sets configuration for parallel lines to axis.
/// </summary>
public Separator Separator
{
get { return (Separator) GetValue(SeparatorProperty); }
set { SetValue(SeparatorProperty, value); }
}
/// <summary>
/// The show labels property
/// </summary>
public static readonly DependencyProperty ShowLabelsProperty = DependencyProperty.Register(
"ShowLabels", typeof (bool), typeof (Axis),
new PropertyMetadata(true, LabelsVisibilityChanged));
/// <summary>
/// Gets or sets if labels are shown in the axis.
/// </summary>
public bool ShowLabels
{
get { return (bool) GetValue(ShowLabelsProperty); }
set { SetValue(ShowLabelsProperty, value); }
}
/// <summary>
/// The maximum value property
/// </summary>
public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register(
"MaxValue", typeof (double), typeof (Axis),
new PropertyMetadata(double.NaN, UpdateChart()));
/// <summary>
/// Gets or sets axis max value, set it to double.NaN to make this property Auto, default value is double.NaN
/// </summary>
public double MaxValue
{
get { return (double) GetValue(MaxValueProperty); }
set { SetValue(MaxValueProperty, value); }
}
/// <summary>
/// The minimum value property
/// </summary>
public static readonly DependencyProperty MinValueProperty = DependencyProperty.Register(
"MinValue", typeof (double), typeof (Axis),
new PropertyMetadata(double.NaN, UpdateChart()));
/// <summary>
/// Gets or sets axis min value, set it to double.NaN to make this property Auto, default value is double.NaN
/// </summary>
public double MinValue
{
get { return (double)GetValue(MinValueProperty); }
set { SetValue(MinValueProperty, value); }
}
/// <summary>
/// Gets the actual minimum value.
/// </summary>
/// <value>
/// The actual minimum value.
/// </value>
public double ActualMinValue => Model.BotLimit;
/// <summary>
/// Gets the actual maximum value.
/// </summary>
/// <value>
/// The actual maximum value.
/// </value>
public double ActualMaxValue => Model.TopLimit;
/// <summary>
/// The minimum range property
/// </summary>
public static readonly DependencyProperty MinRangeProperty = DependencyProperty.Register(
"MinRange", typeof(double), typeof(Axis), new PropertyMetadata(double.MinValue));
/// <summary>
/// Gets or sets the min range this axis can display, useful to limit user zooming.
/// </summary>
public double MinRange
{
get { return (double) GetValue(MinRangeProperty); }
set { SetValue(MinRangeProperty, value); }
}
/// <summary>
/// The maximum range property
/// </summary>
public static readonly DependencyProperty MaxRangeProperty = DependencyProperty.Register(
"MaxRange", typeof(double), typeof(Axis), new PropertyMetadata(double.MaxValue));
/// <summary>
/// Gets or sets the max range this axis can display, useful to limit user zooming.
/// </summary>
public double MaxRange
{
get { return (double) GetValue(MaxRangeProperty); }
set { SetValue(MaxRangeProperty, value); }
}
/// <summary>
/// The title property
/// </summary>
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
"Title", typeof(string), typeof(Axis),
new PropertyMetadata(null, UpdateChart()));
/// <summary>
/// Gets or sets axis title, the title will be displayed only if this property is not null, default is null.
/// </summary>
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
/// <summary>
/// The position property
/// </summary>
public static readonly DependencyProperty PositionProperty = DependencyProperty.Register(
"Position", typeof (AxisPosition), typeof (Axis),
new PropertyMetadata(default(AxisPosition), UpdateChart()));
/// <summary>
/// Gets or sets the axis position, default is Axis.Position.LeftBottom, when the axis is at Y and Position is LeftBottom, then axis will be placed at left, RightTop position will place it at Right, when the axis is at X and position LeftBottom, the axis will be placed at bottom, if position is RightTop then it will be placed at top.
/// </summary>
public AxisPosition Position
{
get { return (AxisPosition) GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); }
}
/// <summary>
/// The is merged property
/// </summary>
public static readonly DependencyProperty IsMergedProperty = DependencyProperty.Register(
"IsMerged", typeof (bool), typeof (Axis),
new PropertyMetadata(default(bool), UpdateChart()));
/// <summary>
/// Gets or sets if the axis labels should me placed inside the chart, this is useful to save some space.
/// </summary>
public bool IsMerged
{
get { return (bool) GetValue(IsMergedProperty); }
set { SetValue(IsMergedProperty, value); }
}
/// <summary>
/// The bar unit property
/// </summary>
public static readonly DependencyProperty BarUnitProperty = DependencyProperty.Register(
"BarUnit", typeof(double), typeof(Axis), new PropertyMetadata(double.NaN));
/// <summary>
/// Gets or sets the bar's series unit width (rows and columns), this property specifies the value in the chart that any bar should take as width.
/// </summary>
[Obsolete("This property was renamed, please use Unit property instead.")]
public double BarUnit
{
get { return (double) GetValue(BarUnitProperty); }
set { SetValue(BarUnitProperty, value); }
}
/// <summary>
/// The unit property
/// </summary>
public static readonly DependencyProperty UnitProperty = DependencyProperty.Register(
"Unit", typeof(double), typeof(Axis), new PropertyMetadata(double.NaN));
/// <summary>
/// Gets or sets the axis unit, setting this property to your actual scale unit (seconds, minutes or any other scale) helps you to fix possible visual issues.
/// </summary>
/// <value>
/// The unit.
/// </value>
public double Unit
{
get { return (double)GetValue(UnitProperty); }
set { SetValue(UnitProperty, value); }
}
/// <summary>
/// The disable animations property
/// </summary>
public static readonly DependencyProperty DisableAnimationsProperty = DependencyProperty.Register(
"DisableAnimations", typeof (bool), typeof (Axis), new PropertyMetadata(default(bool), UpdateChart(true)));
/// <summary>
/// Gets or sets if the axis is animated.
/// </summary>
public bool DisableAnimations
{
get { return (bool) GetValue(DisableAnimationsProperty); }
set { SetValue(DisableAnimationsProperty, value); }
}
/// <summary>
/// The font family property
/// </summary>
public static readonly DependencyProperty FontFamilyProperty =
DependencyProperty.Register("FontFamily", typeof(FontFamily), typeof(Axis),
new PropertyMetadata(new FontFamily("Calibri")));
/// <summary>
/// Gets or sets labels font family, font to use for any label in this axis
/// </summary>
public FontFamily FontFamily
{
get { return (FontFamily)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
/// <summary>
/// The font size property
/// </summary>
public static readonly DependencyProperty FontSizeProperty =
DependencyProperty.Register("FontSize", typeof(double), typeof(Axis), new PropertyMetadata(11.0));
/// <summary>
/// Gets or sets labels font size
/// </summary>
public double FontSize
{
get { return (double)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
/// <summary>
/// The font weight property
/// </summary>
public static readonly DependencyProperty FontWeightProperty =
DependencyProperty.Register("FontWeight", typeof(FontWeight), typeof(Axis),
new PropertyMetadata(FontWeights.Normal));
/// <summary>
/// Gets or sets labels font weight
/// </summary>
public FontWeight FontWeight
{
get { return (FontWeight)GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
/// <summary>
/// The font style property
/// </summary>
public static readonly DependencyProperty FontStyleProperty =
DependencyProperty.Register("FontStyle", typeof(FontStyle), typeof(Axis),
new PropertyMetadata(FontStyle.Normal));
/// <summary>
/// Gets or sets labels font style
/// </summary>
public FontStyle FontStyle
{
get { return (FontStyle)GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
/// <summary>
/// The font stretch property
/// </summary>
public static readonly DependencyProperty FontStretchProperty =
DependencyProperty.Register("FontStretch", typeof(FontStretch), typeof(Axis),
new PropertyMetadata(FontStretch.Normal));
/// <summary>
/// Gets or sets labels font stretch
/// </summary>
public FontStretch FontStretch
{
get { return (FontStretch)GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
/// <summary>
/// The foreground property
/// </summary>
public static readonly DependencyProperty ForegroundProperty =
DependencyProperty.Register("Foreground", typeof(Brush), typeof(Axis), new PropertyMetadata(new SolidColorBrush(Color.FromArgb(255, 170, 170, 170))));
/// <summary>
/// Gets or sets labels text color.
/// </summary>
public Brush Foreground
{
get { return (Brush)GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
/// <summary>
/// The labels rotation property
/// </summary>
public static readonly DependencyProperty LabelsRotationProperty = DependencyProperty.Register(
"LabelsRotation", typeof (double), typeof (Axis), new PropertyMetadata(default(double), UpdateChart()));
/// <summary>
/// Gets or sets the labels rotation in the axis, the angle starts as a horizontal line, you can use any angle in degrees, even negatives.
/// </summary>
public double LabelsRotation
{
get { return (double) GetValue(LabelsRotationProperty); }
set { SetValue(LabelsRotationProperty, value); }
}
/// <summary>
/// The is enabled property
/// </summary>
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.Register(
"IsEnabled", typeof(bool), typeof(Axis), new PropertyMetadata(default(bool)));
/// <summary>
/// Gets or sets a value indicating whether this instance is enabled.
/// </summary>
/// <value>
/// <c>true</c> if this instance is enabled; otherwise, <c>false</c>.
/// </value>
public bool IsEnabled
{
get { return (bool) GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
/// <summary>
/// The axis orientation property
/// </summary>
public static readonly DependencyProperty AxisOrientationProperty = DependencyProperty.Register(
"AxisOrientation", typeof(AxisOrientation), typeof(Axis), new PropertyMetadata(default(AxisOrientation)));
/// <summary>
/// Gets or sets the element orientation ind the axis
/// </summary>
public AxisOrientation AxisOrientation
{
get { return (AxisOrientation)GetValue(AxisOrientationProperty); }
internal set { SetValue(AxisOrientationProperty, value); }
}
#endregion
#region Public Methods
/// <summary>
/// Cleans this instance.
/// </summary>
public void Clean()
{
if (Model == null) return;
Model.ClearSeparators();
Model.Chart.View.RemoveFromView(TitleBlock);
Sections.Clear();
TitleBlock = null;
}
/// <summary>
/// Sets the range.
/// </summary>
/// <param name="min">The minimum.</param>
/// <param name="max">The maximum.</param>
public void SetRange(double min, double max)
{
var bMax = double.IsNaN(MaxValue) ? Model.TopLimit : MaxValue;
var bMin = double.IsNaN(MinValue) ? Model.BotLimit : MinValue;
var nMax = double.IsNaN(MaxValue) ? Model.TopLimit : MaxValue;
var nMin = double.IsNaN(MinValue) ? Model.BotLimit : MinValue;
var e = new RangeChangedEventArgs
{
Range = nMax - nMin,
RightLimitChange = bMax - nMax,
LeftLimitChange = bMin - nMin,
Axis = this
};
var pe = new PreviewRangeChangedEventArgs(e)
{
PreviewMaxValue = max,
PreviewMinValue = min
};
OnPreviewRangeChanged(pe);
if (pe.Cancel) return;
MaxValue = max;
MinValue = min;
Model.Chart.Updater.Run();
OnRangeChanged(e);
}
/// <summary>
/// Renders the separator.
/// </summary>
/// <param name="model">The model.</param>
/// <param name="chart">The chart.</param>
public void RenderSeparator(SeparatorElementCore model, ChartCore chart)
{
AxisSeparatorElement ase;
if (model.View == null)
{
ase = new AxisSeparatorElement(model)
{
Line = BindALine(),
TextBlock = BindATextBlock()
};
model.View = ase;
chart.View.AddToView(ase.Line);
chart.View.AddToView(ase.TextBlock);
Canvas.SetZIndex(ase.Line, -1);
}
else
{
ase = (AxisSeparatorElement) model.View;
}
ase.Line.Visibility = !Separator.IsEnabled ? Visibility.Collapsed : Visibility.Visible;
ase.TextBlock.Visibility = !ShowLabels ? Visibility.Collapsed : Visibility.Visible;
}
/// <summary>
/// Updates the title.
/// </summary>
/// <param name="chart">The chart.</param>
/// <param name="rotationAngle">The rotation angle.</param>
/// <returns></returns>
public CoreSize UpdateTitle(ChartCore chart, double rotationAngle = 0)
{
if (TitleBlock.Parent == null)
{
if (Math.Abs(rotationAngle) > 1)
TitleBlock.RenderTransform = new RotateTransform {Angle = rotationAngle};
chart.View.AddToView(TitleBlock);
}
TitleBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
return string.IsNullOrWhiteSpace(Title)
? new CoreSize()
: new CoreSize(TitleBlock.DesiredSize.Width, TitleBlock.DesiredSize.Height);
}
/// <summary>
/// Sets the title top.
/// </summary>
/// <param name="value">The value.</param>
public void SetTitleTop(double value)
{
Canvas.SetTop(TitleBlock, value);
}
/// <summary>
/// Sets the title left.
/// </summary>
/// <param name="value">The value.</param>
public void SetTitleLeft(double value)
{
Canvas.SetLeft(TitleBlock, value);
}
/// <summary>
/// Gets the title left.
/// </summary>
/// <returns></returns>
public double GetTitleLeft()
{
return Canvas.GetLeft(TitleBlock);
}
/// <summary>
/// Gets the tile top.
/// </summary>
/// <returns></returns>
public double GetTileTop()
{
return Canvas.GetTop(TitleBlock);
}
/// <summary>
/// Gets the size of the label.
/// </summary>
/// <returns></returns>
public CoreSize GetLabelSize()
{
return new CoreSize(TitleBlock.DesiredSize.Width, TitleBlock.DesiredSize.Height);
}
/// <summary>
/// Ases the core element.
/// </summary>
/// <param name="chart">The chart.</param>
/// <param name="source">The source.</param>
/// <returns></returns>
public virtual AxisCore AsCoreElement(ChartCore chart, AxisOrientation source)
{
if (Model == null) Model = new AxisCore(this);
Model.ShowLabels = ShowLabels;
Model.Chart = chart;
Model.IsMerged = IsMerged;
Model.Labels = Labels;
Model.LabelFormatter = LabelFormatter;
Model.MaxValue = MaxValue;
Model.MinValue = MinValue;
Model.Title = Title;
Model.Position = Position;
Model.Separator = Separator.AsCoreElement(Model, source);
Model.DisableAnimations = DisableAnimations;
Model.Sections = Sections.Select(x => x.AsCoreElement(Model, source)).ToList();
return Model;
}
#endregion
internal TextBlock BindATextBlock()
{
var tb = new TextBlock();
tb.SetBinding(TextBlock.FontFamilyProperty,
new Binding { Path = new PropertyPath("FontFamily"), Source = this });
tb.SetBinding(TextBlock.FontSizeProperty,
new Binding { Path = new PropertyPath("FontSize"), Source = this });
tb.SetBinding(TextBlock.FontStretchProperty,
new Binding { Path = new PropertyPath("FontStretch"), Source = this });
tb.SetBinding(TextBlock.FontStyleProperty,
new Binding { Path = new PropertyPath("FontStyle"), Source = this });
tb.SetBinding(TextBlock.FontWeightProperty,
new Binding { Path = new PropertyPath("FontWeight"), Source = this });
tb.SetBinding(TextBlock.ForegroundProperty,
new Binding { Path = new PropertyPath("Foreground"), Source = this });
return tb;
}
internal Line BindALine()
{
var l = new Line();
var s = Separator as Separator;
if (s == null) return l;
l.SetBinding(Shape.StrokeProperty,
new Binding {Path = new PropertyPath("Stroke"), Source = s});
try
{
l.SetBinding(Shape.StrokeDashArrayProperty,
new Binding { Path = new PropertyPath("StrokeDashArray"), Source = s });
}
catch (Exception)
{
// temporarily ignore it
}
l.SetBinding(Shape.StrokeThicknessProperty,
new Binding {Path = new PropertyPath("StrokeThickness"), Source = s});
l.SetBinding(VisibilityProperty,
new Binding {Path = new PropertyPath("Visibility"), Source = s});
return l;
}
/// <summary>
/// Updates the chart.
/// </summary>
/// <param name="animate">if set to <c>true</c> [animate].</param>
/// <param name="updateNow">if set to <c>true</c> [update now].</param>
/// <returns></returns>
protected static PropertyChangedCallback UpdateChart(bool animate = false, bool updateNow = false)
{
return (o, args) =>
{
var wpfAxis = o as Axis;
if (wpfAxis == null) return;
wpfAxis.Model?.Chart.Updater.Run(animate, updateNow);
};
}
private static void LabelsVisibilityChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var axis = (Axis) dependencyObject;
if (axis.Model == null) return;
foreach (var separator in axis.Model.CurrentSeparators)
{
var s = (AxisSeparatorElement) separator.View;
s.TextBlock.Visibility = axis.ShowLabels
? Visibility.Visible
: Visibility.Collapsed;
}
UpdateChart()(dependencyObject, dependencyPropertyChangedEventArgs);
}
/// <summary>
/// Raises the <see cref="E:RangeChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="RangeChangedEventArgs"/> instance containing the event data.</param>
protected void OnRangeChanged(RangeChangedEventArgs e)
{
RangeChanged?.Invoke(e);
if (RangeChangedCommand != null && RangeChangedCommand.CanExecute(e))
RangeChangedCommand.Execute(e);
}
/// <summary>
/// Raises the <see cref="E:PreviewRangeChanged" /> event.
/// </summary>
/// <param name="e">The <see cref="PreviewRangeChangedEventArgs"/> instance containing the event data.</param>
protected void OnPreviewRangeChanged(PreviewRangeChangedEventArgs e)
{
PreviewRangeChanged?.Invoke(e);
if (PreviewRangeChangedCommand != null && PreviewRangeChangedCommand.CanExecute(e))
PreviewRangeChangedCommand.Execute(e);
}
}
}