Files
ShrlAlgoToolkit/WPFDark/Controls/BiaNumberEditor.xaml.cs
ShrlAlgo 4d35cadb56 更新
2025-07-11 09:20:23 +08:00

1436 lines
46 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using WPFDark.Internals;
namespace WPFDark.Controls
{
public enum BiaNumberEditorMode
{
Simple,
WideRange
}
public class BiaNumberEditor : FrameworkElement
{
#region IsReadOnly
public bool IsReadOnly
{
get => _IsReadOnly;
set
{
if (value != _IsReadOnly)
SetValue(IsReadOnlyProperty, value);
}
}
private bool _IsReadOnly;
public static readonly DependencyProperty IsReadOnlyProperty =
DependencyProperty.Register(nameof(IsReadOnly), typeof(bool), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._IsReadOnly = (bool) e.NewValue;
}));
#endregion
#region BorderColor
public ByteColor BorderColor
{
get => _BorderColor;
set
{
if (value != _BorderColor)
SetValue(BorderColorProperty, value);
}
}
private ByteColor _BorderColor = ByteColor.Red;
public static readonly DependencyProperty BorderColorProperty =
DependencyProperty.Register(nameof(BorderColor), typeof(ByteColor), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
Boxes.ByteColorRed,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._BorderColor = (ByteColor) e.NewValue;
}));
#endregion
#region SliderBrush
public Brush? SliderBrush
{
get => _SliderBrush;
set
{
if (value != _SliderBrush)
SetValue(SliderBrushProperty, value);
}
}
private Brush? _SliderBrush = Brushes.GreenYellow;
public static readonly DependencyProperty SliderBrushProperty =
DependencyProperty.Register(nameof(SliderBrush), typeof(Brush), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
Brushes.GreenYellow,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._SliderBrush = (Brush) e.NewValue;
}));
#endregion
#region Value
public double Value
{
get => _Value;
set
{
if (NumberHelper.AreClose(value, _Value) == false)
SetValue(ValueProperty, value);
}
}
private double _Value;
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(double), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
// 最小値・最大値でクランプして保存する
var self = (BiaNumberEditor) s;
self._Value = self.ClampValue((double) e.NewValue);
self.ValueChanged?.Invoke(self, EventArgs.Empty);
}));
#endregion
#region Caption
public string? Caption
{
get => _Caption;
set
{
if (value != _Caption)
SetValue(CaptionProperty, value);
}
}
private string? _Caption;
public static readonly DependencyProperty CaptionProperty =
DependencyProperty.Register(nameof(Caption), typeof(string), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
default(string),
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._Caption = e.NewValue?.ToString() ?? "";
}));
#endregion
#region SliderMinimum
public double SliderMinimum
{
get => _SliderMinimum;
set
{
if (NumberHelper.AreClose(value, _SliderMinimum) == false)
SetValue(SliderMinimumProperty, value);
}
}
private double _SliderMinimum = double.MinValue;
public static readonly DependencyProperty SliderMinimumProperty =
DependencyProperty.Register(nameof(SliderMinimum), typeof(double), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
double.MinValue,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._SliderMinimum = (double) e.NewValue;
}));
#endregion
#region SliderMaximum
public double SliderMaximum
{
get => _SliderMaximum;
set
{
if (NumberHelper.AreClose(value, _SliderMaximum) == false)
SetValue(SliderMaximumProperty, value);
}
}
private double _SliderMaximum = double.MaxValue;
public static readonly DependencyProperty SliderMaximumProperty =
DependencyProperty.Register(nameof(SliderMaximum), typeof(double), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
double.MaxValue,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._SliderMaximum = (double) e.NewValue;
}));
#endregion
#region Minimum
public double Minimum
{
get => _Minimum;
set
{
if (NumberHelper.AreClose(value, _Minimum) == false)
SetValue(MinimumProperty, value);
}
}
private double _Minimum = double.MinValue;
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register(nameof(Minimum), typeof(double), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
double.MinValue,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
// 変更後の最小値でValueをクランプする
var self = (BiaNumberEditor) s;
self._Minimum = (double) e.NewValue;
self.Value = self.ClampValue(self.Value);
}));
#endregion
#region Maximum
public double Maximum
{
get => _Maximum;
set
{
if (NumberHelper.AreClose(value, _Maximum) == false)
SetValue(MaximumProperty, value);
}
}
private double _Maximum = double.MaxValue;
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register(nameof(Maximum), typeof(double), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
double.MaxValue,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
// 変更後の最大値でValueをクランプする
var self = (BiaNumberEditor) s;
self._Maximum = (double) e.NewValue;
self.Value = self.ClampValue(self.Value);
}));
#endregion
#region DisplayFormat
public string DisplayFormat
{
get => _DisplayFormat;
set
{
if (value != _DisplayFormat)
SetValue(DisplayFormatProperty, value);
}
}
private string _DisplayFormat = "F3";
public static readonly DependencyProperty DisplayFormatProperty =
DependencyProperty.Register(nameof(DisplayFormat), typeof(string), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
"F3",
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._DisplayFormat = e.NewValue?.ToString() ?? "";
}));
#endregion
#region UnitString
public string UnitString
{
get => _UnitString;
set
{
if (value != _UnitString)
SetValue(UnitStringProperty, value);
}
}
private string _UnitString = "";
public static readonly DependencyProperty UnitStringProperty =
DependencyProperty.Register(nameof(UnitString), typeof(string), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._UnitString = e.NewValue?.ToString() ?? "";
}));
#endregion
#region Background
public Brush? Background
{
get => _Background;
set
{
if (value != _Background)
SetValue(BackgroundProperty, value);
}
}
private Brush? _Background;
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register(nameof(Background), typeof(Brush), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
default(Brush),
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._Background = (Brush) e.NewValue;
}));
#endregion
#region Foreground
public Brush? Foreground
{
get => _Foreground;
set
{
if (value != _Foreground)
SetValue(ForegroundProperty, value);
}
}
private Brush? _Foreground;
public static readonly DependencyProperty ForegroundProperty =
DependencyProperty.Register(nameof(Foreground), typeof(Brush), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
default(Brush),
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._Foreground = (Brush) e.NewValue;
}));
#endregion
#region CaptionForeground
public Brush? CaptionForeground
{
get => _CaptionForeground;
set
{
if (value != _CaptionForeground)
SetValue(CaptionForegroundProperty, value);
}
}
private Brush? _CaptionForeground;
public static readonly DependencyProperty CaptionForegroundProperty =
DependencyProperty.Register(nameof(CaptionForeground), typeof(Brush), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
default(Brush),
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._CaptionForeground = (Brush) e.NewValue;
}));
#endregion
#region Padding
public Thickness Padding
{
get => _Padding;
set
{
if (value != _Padding)
SetValue(PaddingProperty, value);
}
}
private Thickness _Padding;
public static readonly DependencyProperty PaddingProperty =
DependencyProperty.Register(nameof(Padding), typeof(Thickness), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
new Thickness(0),
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._Padding = (Thickness) e.NewValue;
}));
#endregion
#region Mode
public BiaNumberEditorMode Mode
{
get => _Mode;
set
{
if (value != _Mode)
SetValue(ModeProperty, value);
}
}
private BiaNumberEditorMode _Mode = BiaNumberEditorMode.Simple;
public static readonly DependencyProperty ModeProperty =
DependencyProperty.Register(nameof(Mode), typeof(BiaNumberEditorMode), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
BiaNumberEditorMode.Simple,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._Mode = (BiaNumberEditorMode) e.NewValue;
}));
#endregion
#region Increment
public double Increment
{
get => _Increment;
set
{
if (NumberHelper.AreClose(value, _Increment) == false)
SetValue(IncrementProperty, value);
}
}
private double _Increment = 1.0;
public static readonly DependencyProperty IncrementProperty =
DependencyProperty.Register(nameof(Increment), typeof(double), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
1.0,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._Increment = (double) e.NewValue;
}));
#endregion
#region CornerRadius
public double CornerRadius
{
get => _CornerRadius;
set
{
if (NumberHelper.AreClose(value, _CornerRadius) == false)
SetValue(CornerRadiusProperty, value);
}
}
private double _CornerRadius = Constants.BasicCornerRadiusPrim;
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(nameof(CornerRadius), typeof(double), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
Boxes.BasicCornerRadiusPrim,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._CornerRadius = (double) e.NewValue;
}));
#endregion
#region IsVisibleBorder
public bool IsVisibleBorder
{
get => _IsVisibleBorder;
set
{
if (value != _IsVisibleBorder)
SetValue(IsVisibleBorderProperty, value);
}
}
private bool _IsVisibleBorder = true;
public static readonly DependencyProperty IsVisibleBorderProperty =
DependencyProperty.Register(nameof(IsVisibleBorder), typeof(bool), typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
true,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._IsVisibleBorder = (bool) e.NewValue;
}));
#endregion
#region StartedContinuousEditingCommand
public ICommand? StartedContinuousEditingCommand
{
get => _StartedContinuousEditingCommand;
set
{
if (value != _StartedContinuousEditingCommand)
SetValue(StartedContinuousEditingCommandProperty, value);
}
}
private ICommand? _StartedContinuousEditingCommand;
public static readonly DependencyProperty StartedContinuousEditingCommandProperty =
DependencyProperty.Register(
nameof(StartedContinuousEditingCommand),
typeof(ICommand),
typeof(BiaNumberEditor),
new PropertyMetadata(
default(ICommand),
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._StartedContinuousEditingCommand = (ICommand) e.NewValue;
}));
#endregion
#region EndContinuousEditingCommand
public ICommand? EndContinuousEditingCommand
{
get => _EndContinuousEditingCommand;
set
{
if (value != _EndContinuousEditingCommand)
SetValue(EndContinuousEditingCommandProperty, value);
}
}
private ICommand? _EndContinuousEditingCommand;
public static readonly DependencyProperty EndContinuousEditingCommandProperty =
DependencyProperty.Register(
nameof(EndContinuousEditingCommand),
typeof(ICommand),
typeof(BiaNumberEditor),
new PropertyMetadata(
default(ICommand),
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._EndContinuousEditingCommand = (ICommand) e.NewValue;
}));
#endregion
#region TextTrimming
public BiaTextTrimmingMode TextTrimming
{
get => _TextTrimming;
set
{
if (value != _TextTrimming)
SetValue(TextTrimmingProperty, value);
}
}
private BiaTextTrimmingMode _TextTrimming = BiaTextTrimmingMode.Standard;
public static readonly DependencyProperty TextTrimmingProperty =
DependencyProperty.Register(
nameof(TextTrimming),
typeof(BiaTextTrimmingMode),
typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
BiaTextTrimmingMode.Standard,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._TextTrimming = (BiaTextTrimmingMode) e.NewValue;
}));
#endregion
#region CaptionAlignment
public TextAlignment CaptionAlignment
{
get => _CaptionAlignment;
set
{
if (value != _CaptionAlignment)
SetValue(CaptionAlignmentProperty, value);
}
}
private TextAlignment _CaptionAlignment = TextAlignment.Left;
public static readonly DependencyProperty CaptionAlignmentProperty =
DependencyProperty.Register(
nameof(CaptionAlignment),
typeof(TextAlignment),
typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
TextAlignment.Left,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._CaptionAlignment = (TextAlignment)e.NewValue;
}));
#endregion
#region ValueAlignment
public TextAlignment ValueAlignment
{
get => _ValueAlignment;
set
{
if (value != _ValueAlignment)
SetValue(CaptionAlignmentProperty,value);
}
}
private TextAlignment _ValueAlignment = TextAlignment.Right;
public static readonly DependencyProperty ValueAlignmentProperty =
DependencyProperty.Register(
nameof(ValueAlignment),
typeof(TextAlignment),
typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(
TextAlignment.Right,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
(s, e) =>
{
var self = (BiaNumberEditor) s;
self._ValueAlignment = (TextAlignment)e.NewValue;
}));
#endregion
// ReSharper disable once EventNeverSubscribedTo.Global
public event EventHandler? ValueChanged;
static BiaNumberEditor()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BiaNumberEditor),
new FrameworkPropertyMetadata(typeof(BiaNumberEditor)));
}
public BiaNumberEditor()
{
IsEnabledChanged += (_, _) => InvalidateVisual();
}
protected override void OnRender(DrawingContext dc)
{
if (ActualWidth <= 1 ||
ActualHeight <= 1)
return;
var rounder = new LayoutRounder(this);
DrawBackground(rounder, dc);
var isCornerRadiusZero = NumberHelper.AreCloseZero(CornerRadius);
if (isCornerRadiusZero == false)
dc.PushClip(
Caches.GetClipGeom(rounder, ActualWidth, ActualHeight, CornerRadius, IsVisibleBorder));
{
if (Mode == BiaNumberEditorMode.Simple)
DrawSlider(rounder, dc);
DrawText(dc);
if (IsMouseOver &&
IsEnabled &&
IsReadOnly == false &&
NumberHelper.AreCloseZero(Increment) == false)
DrawSpin(dc);
}
if (isCornerRadiusZero == false)
dc.Pop();
if (IsVisibleBorder)
DrawBorder(rounder, dc);
}
private void DrawBackground(in LayoutRounder rounder, DrawingContext dc)
{
var brush = isEditing
? textBox?.Background
: Background;
if (brush is null)
return;
if (NumberHelper.AreCloseZero(CornerRadius))
dc.DrawRectangle(
brush,
null,
rounder.RoundRenderRectangle(IsVisibleBorder));
else
dc.DrawRoundedRectangle(
brush,
null,
rounder.RoundRenderRectangle(IsVisibleBorder),
CornerRadius,
CornerRadius);
}
private void DrawBorder(in LayoutRounder rounder, DrawingContext dc)
{
if (NumberHelper.AreCloseZero(CornerRadius))
dc.DrawRectangle(
Brushes.Transparent,
rounder.GetBorderPen(BorderColor),
rounder.RoundRenderRectangle(IsVisibleBorder)
);
else
dc.DrawRoundedRectangle(
Brushes.Transparent,
rounder.GetBorderPen(BorderColor),
rounder.RoundRenderRectangle(IsVisibleBorder),
CornerRadius,
CornerRadius);
}
private void DrawSlider(in LayoutRounder rounder, DrawingContext dc)
{
if (SliderWidth <= 0.0f)
return;
var brush = isEditing
? textBox?.Background
: SliderBrush;
if (brush is null)
return;
var w = (UiValue - ActualSliderMinimum) * rounder.RoundRenderWidth(IsVisibleBorder) / SliderWidth;
var r = rounder.RoundRenderRectangle(IsVisibleBorder);
r.Width = (rounder.RoundLayoutValue(w), 0.0).Max();
dc.DrawRectangle(brush, null, r);
}
private const double SpinWidth = 14.0;
private void DrawText(DrawingContext dc)
{
var offsetY = (ActualHeight - Constants.BasicOneLineHeight) * 0.5;
var textWidth = ActualWidth - Padding.Left - Padding.Right - SpinWidth * 2;
var textX = Padding.Left + SpinWidth;
var textY = Padding.Top + offsetY;
if (Caption != null &&
CaptionForeground != null)
DefaultTextRenderer.Instance.Draw(
this,
Caption,
textX,
textY,
CaptionForeground,
dc,
textWidth,
CaptionAlignment,
TextTrimming,
false);
if (UiValueString != null &&
Foreground != null)
{
DefaultTextRenderer.Instance.Draw(
this,
UiValueString,
textX,
textY,
Foreground,
dc,
textWidth,
ValueAlignment,
BiaTextTrimmingMode.None,
false);
}
}
private static readonly Brush? _moBrush = ThemeManager.Current.TryFindResource("AccentBrushKey") as Brush;
private void DrawSpin(DrawingContext dc)
{
if (_decSpinGeom is null)
SetupSpinGeom();
var spinBackground = Caches.GetSolidColorBrush(new ByteColor(0x40, 0x00, 0x00, 0x00));
var offsetY = 8 + (ActualHeight - Constants.BasicOneLineHeight) * 0.5;
{
var offsetX = 5.0;
if (_mouseOverType == MouseOverType.DecSpin)
dc.DrawRectangle(spinBackground, null, new Rect(0, 0, SpinWidth, ActualHeight));
var key = HashCodeMaker.Make(offsetX, offsetY);
if (TranslateTransformCache.TryGetValue(key, out var tt) == false)
{
tt = new TranslateTransform(offsetX, offsetY);
TranslateTransformCache.Add(key, tt);
}
dc.PushTransform(tt);
dc.DrawGeometry(
_mouseOverType == MouseOverType.DecSpin
? _moBrush
: Foreground, null, _decSpinGeom);
dc.Pop();
}
{
var offsetX = ActualWidth - 5.0 * 2 + 1;
if (_mouseOverType == MouseOverType.IncSpin)
dc.DrawRectangle(spinBackground, null,
new Rect(ActualWidth - SpinWidth, 0, SpinWidth, ActualHeight));
var key = HashCodeMaker.Make(offsetX, offsetY);
if (TranslateTransformCache.TryGetValue(key, out var tt) == false)
{
tt = new TranslateTransform(offsetX, offsetY);
TranslateTransformCache.Add(key, tt);
}
dc.PushTransform(tt);
dc.DrawGeometry(
_mouseOverType == MouseOverType.IncSpin
? _moBrush
: Foreground, null, _incSpinGeom);
dc.Pop();
}
}
private enum MouseOverType
{
None,
DecSpin,
IncSpin,
Slider
}
private bool _isMouseDown;
private bool _isMouseMoved;
private Point _oldPos;
private Point _mouseDownPos;
private MouseOverType _mouseOverTypeOnMouseDown;
private MouseOverType _mouseOverType;
private bool _isContinuousEdited;
private double _ContinuousEditingStartValue;
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
_oldPos = e.GetPosition(this);
_mouseDownPos = _oldPos;
if (IsReadOnly)
return;
_isMouseDown = true;
_isMouseMoved = false;
_mouseOverTypeOnMouseDown = MakeMouseOverType(e);
_isContinuousEdited = false;
_ContinuousEditingStartValue = Value;
if (_mouseOverTypeOnMouseDown == MouseOverType.Slider)
CaptureMouse();
e.Handled = true;
}
private const double ClickPlayWidth = 4;
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
var newMouseOverType = MakeMouseOverType(e);
if (_mouseOverType != newMouseOverType)
{
_mouseOverType = newMouseOverType;
InvalidateVisual();
}
if (_isMouseDown == false)
{
Cursor = _mouseOverType == MouseOverType.Slider && IsReadOnly == false ? Cursors.SizeWE : Cursors.Arrow;
return;
}
if (IsReadOnly)
return;
// Down直後マウス移動ない場合でもOnMouseMoveが呼ばれることがあるため、判定する。
var currentPos = e.GetPosition(this);
if (currentPos == _oldPos)
return;
if (_isMouseMoved == false)
if (Math.Abs(currentPos.X - _mouseDownPos.X) <= ClickPlayWidth)
return;
if (_mouseOverTypeOnMouseDown != MouseOverType.Slider)
return;
var oldValue = Value;
var newValue = Value;
switch (Mode)
{
case BiaNumberEditorMode.Simple:
{
// 0から1
var xr = (currentPos.X, 0.0, ActualWidth).Clamp() / ActualWidth;
newValue = SliderWidth * xr + ActualSliderMinimum;
break;
}
case BiaNumberEditorMode.WideRange:
{
// Ctrl押下中は倍速い
var s = KeyboardHelper.IsPressControl
? 5.0
: 1.0;
var w = currentPos.X - _oldPos.X;
var v = oldValue + s * w * Increment;
newValue = (v, ActualSliderMinimum, ActualSliderMaximum).Clamp();
break;
}
}
if (NumberHelper.AreClose(newValue, oldValue) == false)
{
if (_isContinuousEdited == false)
{
_isContinuousEdited = true;
StartedContinuousEditingCommand?.ExecuteIfCan(null);
}
Value = newValue;
}
_mouseOverType = MouseOverType.Slider;
_isMouseMoved = true;
_oldPos = currentPos;
Cursor = _mouseOverType == MouseOverType.Slider && IsReadOnly == false ? Cursors.SizeWE : Cursors.Arrow;
e.Handled = true;
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
if (IsReadOnly == false)
{
if (_mouseOverTypeOnMouseDown == MouseOverType.Slider)
ReleaseMouseCapture();
}
_isMouseDown = false;
if (_isMouseMoved)
{
if (_isContinuousEdited)
{
if (EndContinuousEditingCommand != null)
{
if (EndContinuousEditingCommand.CanExecute(null))
{
var changedValue = Value;
Value = _ContinuousEditingStartValue;
EndContinuousEditingCommand.Execute(null);
Value = changedValue;
}
}
_isContinuousEdited = false;
}
}
else
{
var p = e.GetPosition(this);
if (Math.Abs(p.X - _mouseDownPos.X) <= ClickPlayWidth)
{
// Ctrl押下中は倍速い
var inc = KeyboardHelper.IsPressControl
? Increment * 5
: Increment;
if (p.X <= SpinWidth && IsReadOnly == false)
AddValue(-inc);
else if (p.X >= ActualWidth - SpinWidth && IsReadOnly == false)
AddValue(inc);
else
ShowEditBox();
}
}
_mouseOverType = MakeMouseOverType(e);
e.Handled = true;
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
if (_isMouseDown)
{
_isMouseDown = false;
ReleaseMouseCapture();
this.ResetMouseClipping();
}
_mouseOverType = MouseOverType.None;
e.Handled = true;
}
private MouseOverType MakeMouseOverType(MouseEventArgs e)
{
var x = e.GetPosition(this).X;
if (x < 0 || x >= ActualWidth)
return MouseOverType.None;
if (x <= SpinWidth)
return MouseOverType.DecSpin;
if (x >= ActualWidth - SpinWidth)
return MouseOverType.IncSpin;
return MouseOverType.Slider;
}
private TextBox? textBox;
private bool isEditing;
private void AddValue(double i)
{
var v = Value + i;
Value = ClampValue(v);
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (IsEnabled == false)
return;
if (e.Key == Key.Return)
{
ShowEditBox();
e.Handled = true;
}
}
private void ShowEditBox()
{
if (isEditing)
return;
if (textBox is null)
{
textBox = new TextBox
{
IsTabStop = false,
IsUndoEnabled = false,
FocusVisualStyle = null,
SelectionLength = 0
};
textBox.TextChanged += TextBox_OnTextChanged;
textBox.LostFocus += TextBox_OnLostFocus;
textBox.PreviewKeyDown += TextBox_OnPreviewKeyDown;
textBox.PreviewMouseDown += TextBox_OnPreviewMouseDown;
}
textBox.Width = ActualWidth;
textBox.Height = ActualHeight;
textBox.IsReadOnly = IsReadOnly;
textBox.Text = FormattedValueString;
AddVisualChild(textBox);
InvalidateMeasure();
textBox.SelectAll();
textBox.Focus();
textBox.CaptureMouse();
isEditing = true;
}
private void TextBox_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var rounder = new LayoutRounder(this);
// 自コントロール上であれば、終了させない
var pos = e.GetPosition(this);
var rect = rounder.RoundRenderRectangle(false);
if (rect.Contains(pos))
return;
if (isEditing)
FinishEditing(true);
}
private void TextBox_OnTextChanged(object sender, TextChangedEventArgs e)
{
InvalidateVisual();
}
private void TextBox_OnLostFocus(object sender, RoutedEventArgs e)
{
if (isEditing)
FinishEditing(true);
}
private void TextBox_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Tab:
{
Debug.Assert(textBox != null);
var v = MakeValueFromString(textBox.Text);
if (v.Result == MakeValueResult.Continue)
textBox.Text = v.Value.ToString(DisplayFormat);
else
FinishEditing(v.Result == MakeValueResult.Ok);
var t = Keyboard.Modifiers == ModifierKeys.Shift
? Caches.PreviousTraversalRequest
: Caches.NextTraversalRequest;
MoveFocus(t);
e.Handled = true;
break;
}
case Key.Return:
{
Debug.Assert(textBox != null);
var v = MakeValueFromString(textBox.Text);
if (v.Result == MakeValueResult.Continue)
textBox.Text = v.Value.ToString(DisplayFormat);
else
{
FinishEditing(v.Result == MakeValueResult.Ok);
Dispatcher?.BeginInvoke(DispatcherPriority.Input, (Action) (() => Focus()));
}
e.Handled = true;
break;
}
case Key.Escape:
{
Debug.Assert(textBox != null);
textBox.Text = FormattedValueString;
FinishEditing(false);
Dispatcher?.BeginInvoke(DispatcherPriority.Input, (Action) (() => Focus()));
e.Handled = true;
break;
}
}
}
private void FinishEditing(bool isEdit)
{
Debug.Assert(textBox != null);
if (isEdit)
{
var v = MakeValueFromString(textBox.Text);
if (v.Result == MakeValueResult.Ok ||
v.Result == MakeValueResult.Continue)
Value = v.Value;
}
textBox?.ReleaseMouseCapture();
RemoveVisualChild(textBox);
isEditing = false;
InvalidateVisual();
}
protected override int VisualChildrenCount
=> isEditing
? 1
: 0;
protected override Visual GetVisualChild(int index)
=> textBox ?? throw new NullReferenceException();
protected override Size ArrangeOverride(Size finalSize)
{
if (isEditing)
textBox?.Arrange(new Rect(new Point(0, 0), textBox.DesiredSize));
return base.ArrangeOverride(finalSize);
}
protected override Size MeasureOverride(Size availableSize)
{
if (isEditing)
textBox?.Measure(new Size(ActualWidth, ActualHeight));
// todo:DPI変更時に再描画が行われないため明示的に指示している。要調査。
InvalidateVisual();
return base.MeasureOverride(availableSize);
}
private enum MakeValueResult
{
Ok,
Cancel,
Continue
}
private (MakeValueResult Result, double Value) MakeValueFromString(string src)
{
if (double.TryParse(src, out var v))
return (MakeValueResult.Ok, ClampValue(v));
if (double.TryParse(Evaluator.Eval(src), out v))
return (MakeValueResult.Continue, ClampValue(v));
return (MakeValueResult.Cancel, default);
}
private static void SetupSpinGeom()
{
{
_decSpinGeom = new StreamGeometry();
using (var ctx = _decSpinGeom.Open())
{
ctx.DrawTriangle(
new Point(0, 4),
new Point(4, 0),
new Point(4, 8),
true,
true);
}
_decSpinGeom.Freeze();
}
{
_incSpinGeom = new StreamGeometry();
using (var ctx = _incSpinGeom.Open())
{
ctx.DrawTriangle(
new Point(4, 4),
new Point(0, 0),
new Point(0, 8),
true,
true);
}
_incSpinGeom.Freeze();
}
}
private double ClampValue(double v)
=> (v, ActualMinimum, ActualMaximum).Clamp();
private string FormattedValueString => Value.ToString(DisplayFormat);
private double SliderWidth => Math.Abs(SliderMaximum - SliderMinimum);
private double ActualSliderMinimum => (SliderMinimum, SliderMaximum).Min();
private double ActualSliderMaximum => (SliderMinimum, SliderMaximum).Max();
private double ActualMinimum => (Minimum, Maximum).Min();
private double ActualMaximum => (Minimum, Maximum).Max();
private string? UiValueString
{
get
{
if (isEditing == false)
return Concat(FormattedValueString, UnitString);
var v = MakeValueFromString(textBox?.Text ?? "");
return
v.Result == MakeValueResult.Ok || v.Result == MakeValueResult.Continue
? Concat(v.Value.ToString(DisplayFormat), UnitString)
: Concat(FormattedValueString, UnitString);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string Concat(string a, string b)
=> string.IsNullOrEmpty(b)
? a
: a + b;
private double UiValue
{
get
{
if (isEditing == false)
return Value;
var v = MakeValueFromString(textBox?.Text ?? "");
return
v.Result == MakeValueResult.Ok || v.Result == MakeValueResult.Continue
? v.Value
: Value;
}
}
private static StreamGeometry? _decSpinGeom;
private static StreamGeometry? _incSpinGeom;
private static readonly Dictionary<long, TranslateTransform> TranslateTransformCache = new Dictionary<long, TranslateTransform>();
}
}