diff --git a/HandyControl/Controls/Base/HeaderedSelectableItem.cs b/HandyControl/Controls/Base/HeaderedSelectableItem.cs
new file mode 100644
index 00000000..480be514
--- /dev/null
+++ b/HandyControl/Controls/Base/HeaderedSelectableItem.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+
+namespace HandyControl.Controls
+{
+ public class HeaderedSelectableItem : SelectableItem
+ {
+ public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
+ "Header", typeof(object), typeof(HeaderedSelectableItem), new PropertyMetadata(default(object)));
+
+ public object Header
+ {
+ get => GetValue(HeaderProperty);
+ set => SetValue(HeaderProperty, value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Controls/Base/ISelectable.cs b/HandyControl/Controls/Base/ISelectable.cs
new file mode 100644
index 00000000..ce254ea3
--- /dev/null
+++ b/HandyControl/Controls/Base/ISelectable.cs
@@ -0,0 +1,11 @@
+using System.Windows;
+
+namespace HandyControl.Controls
+{
+ public interface ISelectable
+ {
+ event RoutedEventHandler Selected;
+
+ bool IsSelected { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Controls/Base/RegularItemsControl.cs b/HandyControl/Controls/Base/RegularItemsControl.cs
new file mode 100644
index 00000000..4faca069
--- /dev/null
+++ b/HandyControl/Controls/Base/RegularItemsControl.cs
@@ -0,0 +1,41 @@
+using System.Windows;
+using HandyControl.Data;
+
+namespace HandyControl.Controls
+{
+ ///
+ /// 规则ItemsControl
+ ///
+ ///
+ /// 该类的每一项都具有相同的大小和外边距
+ ///
+ public class RegularItemsControl : SimpleItemsControl
+ {
+ public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register(
+ "ItemWidth", typeof(double), typeof(RegularItemsControl), new PropertyMetadata(ValueBoxes.Double200Box));
+
+ public double ItemWidth
+ {
+ get => (double) GetValue(ItemWidthProperty);
+ set => SetValue(ItemWidthProperty, value);
+ }
+
+ public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(
+ "ItemHeight", typeof(double), typeof(RegularItemsControl), new PropertyMetadata(ValueBoxes.Double200Box));
+
+ public double ItemHeight
+ {
+ get => (double) GetValue(ItemHeightProperty);
+ set => SetValue(ItemHeightProperty, value);
+ }
+
+ public static readonly DependencyProperty ItemMarginProperty = DependencyProperty.Register(
+ "ItemMargin", typeof(Thickness), typeof(RegularItemsControl), new PropertyMetadata(default(Thickness)));
+
+ public Thickness ItemMargin
+ {
+ get => (Thickness) GetValue(ItemMarginProperty);
+ set => SetValue(ItemMarginProperty, value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Controls/Base/SelectableItem.cs b/HandyControl/Controls/Base/SelectableItem.cs
new file mode 100644
index 00000000..a64292d6
--- /dev/null
+++ b/HandyControl/Controls/Base/SelectableItem.cs
@@ -0,0 +1,56 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using HandyControl.Data;
+
+namespace HandyControl.Controls
+{
+ public class SelectableItem : ContentControl, ISelectable
+ {
+ private bool _isMouseLeftButtonDown;
+
+ protected override void OnMouseLeave(MouseEventArgs e)
+ {
+ base.OnMouseLeave(e);
+
+ _isMouseLeftButtonDown = false;
+ }
+
+ protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
+ {
+ base.OnMouseLeftButtonDown(e);
+
+ _isMouseLeftButtonDown = true;
+ }
+
+ protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
+ {
+ base.OnMouseLeftButtonUp(e);
+
+ if (_isMouseLeftButtonDown)
+ {
+ RaiseEvent(new RoutedEventArgs(SelectedEvent, this));
+ _isMouseLeftButtonDown = false;
+ }
+ }
+
+ public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
+ "IsSelected", typeof(bool), typeof(SelectableItem), new PropertyMetadata(ValueBoxes.FalseBox));
+
+ public bool IsSelected
+ {
+ get => (bool)GetValue(IsSelectedProperty);
+ set => SetValue(IsSelectedProperty, value);
+ }
+
+ public static readonly RoutedEvent SelectedEvent =
+ EventManager.RegisterRoutedEvent("Selected", RoutingStrategy.Bubble,
+ typeof(RoutedEventHandler), typeof(SelectableItem));
+
+ public event RoutedEventHandler Selected
+ {
+ add => AddHandler(SelectedEvent, value);
+ remove => RemoveHandler(SelectedEvent, value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Controls/Base/SimpleItemsControl.cs b/HandyControl/Controls/Base/SimpleItemsControl.cs
index d88cf20d..e466722a 100644
--- a/HandyControl/Controls/Base/SimpleItemsControl.cs
+++ b/HandyControl/Controls/Base/SimpleItemsControl.cs
@@ -7,7 +7,9 @@ using System.Windows.Markup;
namespace HandyControl.Controls
{
- [DefaultEvent("OnItemsChanged")]
+ ///
+ /// ItemsControl的轻量级版本
+ ///
[DefaultProperty("Items")]
[ContentProperty("Items")]
[TemplatePart(Name = ElementPanel, Type = typeof(Panel))]
diff --git a/HandyControl/Controls/CoverFlow/CoverFlow.cs b/HandyControl/Controls/CoverFlow/CoverFlow.cs
new file mode 100644
index 00000000..5cbdecaf
--- /dev/null
+++ b/HandyControl/Controls/CoverFlow/CoverFlow.cs
@@ -0,0 +1,338 @@
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Media.Media3D;
+using HandyControl.Data;
+
+namespace HandyControl.Controls
+{
+ ///
+ /// 封面流
+ ///
+ [TemplatePart(Name = ElementViewport3D, Type = typeof(Viewport3D))]
+ [TemplatePart(Name = ElementCamera, Type = typeof(ProjectionCamera))]
+ [TemplatePart(Name = ElementVisualParent, Type = typeof(ModelVisual3D))]
+ public class CoverFlow : Control
+ {
+ private const string ElementViewport3D = "PART_Viewport3D";
+
+ private const string ElementCamera = "PART_Camera";
+
+ private const string ElementVisualParent = "PART_VisualParent";
+
+ ///
+ /// 最大显示数量的一半
+ ///
+ private const int MaxShowCountHalf = 7;
+
+ ///
+ /// 页码
+ ///
+ public static readonly DependencyProperty PageIndexProperty = DependencyProperty.Register(
+ "PageIndex", typeof(int), typeof(CoverFlow),
+ new PropertyMetadata(ValueBoxes.Int0Box, OnPageIndexChanged, CoercePageIndex));
+
+ private static object CoercePageIndex(DependencyObject d, object baseValue)
+ {
+ var ctl = (CoverFlow) d;
+ var v = (int) baseValue;
+
+ if (v < 0)
+ {
+ return 0;
+ }
+ if (v >= ctl._uriList.Count)
+ {
+ return ctl._uriList.Count - 1;
+ }
+ return v;
+ }
+
+ private static void OnPageIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var ctl = (CoverFlow) d;
+ ctl.UpdateIndex((int)e.NewValue, (int)e.OldValue);
+ }
+
+ ///
+ /// 是否循环
+ ///
+ public static readonly DependencyProperty LoopProperty = DependencyProperty.Register(
+ "Loop", typeof(bool), typeof(CoverFlow), new PropertyMetadata(ValueBoxes.FalseBox));
+
+ ///
+ /// 存储所有的资源标识符
+ ///
+ private readonly Dictionary _uriList = new Dictionary();
+
+ ///
+ /// 当前在显示范围内的项
+ ///
+ private readonly Dictionary _itemShowList = new Dictionary();
+
+ ///
+ /// 相机
+ ///
+ private ProjectionCamera _camera;
+
+ ///
+ /// 3d画布
+ ///
+ private Viewport3D _viewport3D;
+
+ ///
+ /// 项容器
+ ///
+ private ModelVisual3D _visualParent;
+
+ ///
+ /// 显示范围内第一个项的编号
+ ///
+ private int _firstShowIndex;
+
+ ///
+ /// 显示范围内最后一个项的编号
+ ///
+ private int _lastShowIndex;
+
+ ///
+ /// 在获取新的模板后是否需要更新项
+ ///
+ private bool _updateItems;
+
+ private bool _isLoaded;
+
+ ///
+ /// 跳转编号
+ ///
+ private int _jumpToIndex = -1;
+
+ public CoverFlow()
+ {
+ Loaded += (s, e) =>
+ {
+ if (_isLoaded) return;
+ _isLoaded = true;
+ if (_updateItems)
+ {
+ UpdateShowRange();
+ _updateItems = false;
+ }
+ if (_jumpToIndex > 0)
+ {
+ PageIndex = _jumpToIndex;
+ }
+ };
+ }
+
+ ///
+ /// 页码
+ ///
+ public int PageIndex
+ {
+ get => (int) GetValue(PageIndexProperty);
+ internal set => SetValue(PageIndexProperty, value);
+ }
+
+ ///
+ /// 是否循环
+ ///
+ public bool Loop
+ {
+ get => (bool)GetValue(LoopProperty);
+ set => SetValue(LoopProperty, value);
+ }
+
+ public override void OnApplyTemplate()
+ {
+ if (_viewport3D != null)
+ {
+ _viewport3D.Children.Clear();
+ _itemShowList.Clear();
+ _viewport3D.MouseLeftButtonDown -= Viewport3D_MouseLeftButtonDown;
+ }
+
+ base.OnApplyTemplate();
+
+ _viewport3D = GetTemplateChild(ElementViewport3D) as Viewport3D;
+ if (_viewport3D != null)
+ {
+ _viewport3D.MouseLeftButtonDown += Viewport3D_MouseLeftButtonDown;
+ }
+
+ _camera = GetTemplateChild(ElementCamera) as ProjectionCamera;
+ _visualParent = GetTemplateChild(ElementVisualParent) as ModelVisual3D;
+
+ if (_isLoaded)
+ {
+ UpdateShowRange();
+ _camera.Position = new Point3D(CoverFlowItem.Interval * PageIndex, _camera.Position.Y,
+ _camera.Position.Z);
+ }
+ }
+
+ ///
+ /// 批量添加资源
+ ///
+ ///
+ public void AddRange(List uriList)
+ {
+ foreach (var uri in uriList)
+ {
+ _uriList.Add(_uriList.Count, uri);
+ }
+ _updateItems = true;
+ }
+
+ ///
+ /// 添加一项资源
+ ///
+ ///
+ public void Add(string uriString)
+ {
+ _uriList.Add(_uriList.Count, new Uri(uriString));
+ _updateItems = true;
+ }
+
+ ///
+ /// 添加一项资源
+ ///
+ ///
+ public void Add(Uri uri)
+ {
+ _uriList.Add(_uriList.Count, uri);
+ _updateItems = true;
+ }
+
+ ///
+ /// 跳转
+ ///
+ public void JumpTo(int index) => _jumpToIndex = index;
+
+ protected override void OnMouseWheel(MouseWheelEventArgs e)
+ {
+ base.OnMouseWheel(e);
+
+ if (e.Delta < 0)
+ {
+ var index = PageIndex + 1;
+ PageIndex = index >= _uriList.Count ? (Loop ? 0 : _uriList.Count - 1) : index;
+ }
+ else
+ {
+ var index = PageIndex - 1;
+ PageIndex = index < 0 ? (Loop ? _uriList.Count - 1 : 0) : index;
+ }
+ }
+
+ ///
+ /// 删除指定位置的项
+ ///
+ ///
+ private void Remove(int pos)
+ {
+ var item = _itemShowList[pos];
+ _visualParent.Children.Remove(item);
+ _itemShowList.Remove(pos);
+ }
+
+ ///
+ /// 移动项到指定的位置
+ ///
+ ///
+ ///
+ private void Move(int index, bool animated) => _itemShowList[index].Move(PageIndex, animated);
+
+ private void Viewport3D_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ var result = (RayMeshGeometry3DHitTestResult) VisualTreeHelper.HitTest(_viewport3D, e.GetPosition(_viewport3D));
+ if (result != null)
+ {
+ foreach (var item in _itemShowList.Values)
+ {
+ if (item.HitTest(result.MeshHit))
+ {
+ PageIndex = item.Index;
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// 更新项的位置
+ ///
+ ///
+ ///
+ private void UpdateIndex(int newIndex, int oldIndex)
+ {
+ var animate = Math.Abs(newIndex - oldIndex) < MaxShowCountHalf;
+ UpdateShowRange();
+ if (newIndex > oldIndex)
+ {
+ if (oldIndex < _firstShowIndex)
+ {
+ oldIndex = _firstShowIndex;
+ }
+ for (var i = oldIndex; i <= newIndex; i++)
+ {
+ Move(i, animate);
+ }
+ }
+ else
+ {
+ if (oldIndex > _lastShowIndex)
+ {
+ oldIndex = _lastShowIndex;
+ }
+ for (var i = oldIndex; i >= newIndex; i--)
+ {
+ Move(i, animate);
+ }
+ }
+ _camera.Position = new Point3D(CoverFlowItem.Interval * newIndex, _camera.Position.Y,
+ _camera.Position.Z);
+ }
+
+ ///
+ /// 更新显示范围
+ ///
+ private void UpdateShowRange()
+ {
+ var newFirstShowIndex = Math.Max(PageIndex - MaxShowCountHalf, 0);
+ var newLastShowIndex = Math.Min(PageIndex + MaxShowCountHalf, _uriList.Count - 1);
+
+ if (_firstShowIndex < newFirstShowIndex)
+ {
+ for (var i = _firstShowIndex; i < newFirstShowIndex; i++)
+ {
+ Remove(i);
+ }
+ }
+ else if (newLastShowIndex < _lastShowIndex)
+ {
+ for (var i = newLastShowIndex; i < _lastShowIndex; i++)
+ {
+ Remove(i);
+ }
+ }
+
+ for (var i = newFirstShowIndex; i <= newLastShowIndex; i++)
+ {
+ if (!_itemShowList.ContainsKey(i))
+ {
+ var cover = new CoverFlowItem(i, PageIndex, BitmapFrame.Create(_uriList[i]));
+ _itemShowList[i] = cover;
+ _visualParent.Children.Add(cover);
+ }
+ }
+
+ _firstShowIndex = newFirstShowIndex;
+ _lastShowIndex = newLastShowIndex;
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Controls/CoverFlow/CoverFlowItem.cs b/HandyControl/Controls/CoverFlow/CoverFlowItem.cs
new file mode 100644
index 00000000..634911a9
--- /dev/null
+++ b/HandyControl/Controls/CoverFlow/CoverFlowItem.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Media3D;
+using HandyControl.Tools;
+
+namespace HandyControl.Controls
+{
+ public class CoverFlowItem : ModelVisual3D
+ {
+ internal static readonly double Interval = .2;
+
+ internal static readonly double AnimationSpeed = 400;
+
+ private readonly AxisAngleRotation3D _rotation3D;
+
+ private readonly TranslateTransform3D _transform3D;
+
+ private readonly Model3DGroup _model3DGroup;
+
+ private readonly ImageSource _source;
+
+ internal int Index { get; set; }
+
+ public CoverFlowItem(int itemIndex, int currentIndex, ImageSource source)
+ {
+ Index = itemIndex;
+ _source = source;
+
+ _rotation3D = new AxisAngleRotation3D(new Vector3D(0, 1, 0), GetAngleByPos(currentIndex));
+ _transform3D = new TranslateTransform3D(GetXByPos(currentIndex), 0, GetZByPos(currentIndex));
+
+ var transformGroup = new Transform3DGroup();
+ transformGroup.Children.Add(new RotateTransform3D(_rotation3D));
+ transformGroup.Children.Add(_transform3D);
+
+ _model3DGroup = new Model3DGroup();
+ _model3DGroup.Children.Add(new GeometryModel3D(CreateItemGeometry(), new DiffuseMaterial(new ImageBrush(_source))));
+ _model3DGroup.Transform = transformGroup;
+
+ Content = _model3DGroup;
+ }
+
+ ///
+ /// 创建网格形状
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static MeshGeometry3D CreateMeshGeometry(Point3D p0, Point3D p1, Point3D p2, Point3D p3)
+ {
+ var positions = new Point3DCollection
+ {
+ p0,
+ p1,
+ p2,
+ p3
+ };
+
+ var triangleIndices = new Int32Collection
+ {
+ 0,
+ 1,
+ 2,
+ 2,
+ 3,
+ 0
+ };
+
+ var textureCoordinates = new PointCollection
+ {
+ new Point(0, 1),
+ new Point(1, 1),
+ new Point(1, 0),
+ new Point(0, 0),
+ new Point(1, 0),
+ new Point(1, 1)
+ };
+
+ var normals = new Vector3DCollection
+ {
+ ArithmeticHelper.CalNormal(p0, p1, p2),
+ ArithmeticHelper.CalNormal(p2, p3, p0)
+ };
+
+ var geometry3D = new MeshGeometry3D
+ {
+ Positions = positions,
+ TriangleIndices = triangleIndices,
+ TextureCoordinates = textureCoordinates,
+ Normals = normals
+ };
+
+ geometry3D.Freeze();
+ return geometry3D;
+ }
+
+ ///
+ /// 创建内容形状
+ ///
+ ///
+ private Geometry3D CreateItemGeometry()
+ {
+ var sx = _source.Width > _source.Height ? 0 : 1 - _source.Width / _source.Height;
+ var sy = _source.Width < _source.Height ? 0 : 1 - _source.Height / _source.Width;
+
+ var p0 = new Point3D(-1 + sx, -1 + sy, 0);
+ var p1 = new Point3D(1 - sx, -1 + sy, 0);
+ var p2 = new Point3D(1 - sx, 1 - sy, 0);
+ var p3 = new Point3D(-1 + sx, 1 - sy, 0);
+
+ return CreateMeshGeometry(p0, p1, p2, p3);
+ }
+
+ private double GetAngleByPos(int index) => Math.Sign(Index - index) * -90;
+
+ private double GetXByPos(int index)
+ {
+ Console.WriteLine(Index * Interval + Math.Sign(Index - index) * 1.5);
+ return Index * Interval + Math.Sign(Index - index) * 1.5;
+ }
+
+ private double GetZByPos(int index) => Index == index ? 1 : 0;
+
+ ///
+ /// 移动
+ ///
+ internal void Move(int currentIndex, bool animated)
+ {
+ if (animated || _rotation3D.HasAnimatedProperties)
+ {
+ _rotation3D.BeginAnimation(AxisAngleRotation3D.AngleProperty,
+ AnimationHelper.CreateAnimation(GetAngleByPos(currentIndex), AnimationSpeed));
+ _transform3D.BeginAnimation(TranslateTransform3D.OffsetXProperty,
+ AnimationHelper.CreateAnimation(GetXByPos(currentIndex), AnimationSpeed));
+ _transform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty,
+ AnimationHelper.CreateAnimation(GetZByPos(currentIndex), AnimationSpeed));
+ }
+ else
+ {
+ _rotation3D.Angle = GetAngleByPos(currentIndex);
+ _transform3D.OffsetX = GetXByPos(currentIndex);
+ _transform3D.OffsetZ = GetZByPos(currentIndex);
+ }
+ }
+
+ ///
+ /// 命中测试
+ ///
+ ///
+ ///
+ internal bool HitTest(MeshGeometry3D mesh)
+ {
+ foreach (var item in _model3DGroup.Children)
+ {
+ if (item is GeometryModel3D geometryModel3D)
+ {
+ if (Equals(geometryModel3D.Geometry, mesh))
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Controls/CoverView/CoverView.cs b/HandyControl/Controls/CoverView/CoverView.cs
index 12e65563..862eb11f 100644
--- a/HandyControl/Controls/CoverView/CoverView.cs
+++ b/HandyControl/Controls/CoverView/CoverView.cs
@@ -4,7 +4,7 @@ using HandyControl.Data;
namespace HandyControl.Controls
{
- public class CoverView : SimpleItemsControl
+ public class CoverView : RegularItemsControl
{
private readonly CoverViewContent _viewContent;
@@ -14,7 +14,7 @@ namespace HandyControl.Controls
{
_viewContent = new CoverViewContent {ContentHeight = ItemContentHeight};
- AddHandler(CoverViewItem.SelectedEvent, new RoutedEventHandler(CoverViewItem_OnSelected));
+ AddHandler(SelectableItem.SelectedEvent, new RoutedEventHandler(CoverViewItem_OnSelected));
_viewContent.SetBinding(WidthProperty, new Binding(ActualWidthProperty.Name) { Source = this });
}
@@ -87,33 +87,6 @@ namespace HandyControl.Controls
set => SetValue(CoverViewContentStyleProperty, value);
}
- public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register(
- "ItemWidth", typeof(double), typeof(CoverView), new PropertyMetadata(ValueBoxes.Double200Box));
-
- public double ItemWidth
- {
- get => (double) GetValue(ItemWidthProperty);
- set => SetValue(ItemWidthProperty, value);
- }
-
- public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(
- "ItemHeight", typeof(double), typeof(CoverView), new PropertyMetadata(ValueBoxes.Double200Box));
-
- public double ItemHeight
- {
- get => (double) GetValue(ItemHeightProperty);
- set => SetValue(ItemHeightProperty, value);
- }
-
- public static readonly DependencyProperty ItemMarginProperty = DependencyProperty.Register(
- "ItemMargin", typeof(Thickness), typeof(CoverView), new PropertyMetadata(default(Thickness)));
-
- public Thickness ItemMargin
- {
- get => (Thickness) GetValue(ItemMarginProperty);
- set => SetValue(ItemMarginProperty, value);
- }
-
internal static readonly DependencyProperty GroupsProperty = DependencyProperty.Register(
"Groups", typeof(int), typeof(CoverView), new FrameworkPropertyMetadata(ValueBoxes.Int5Box, FrameworkPropertyMetadataOptions.AffectsMeasure, OnGroupsChanged), IsGroupsValid);
diff --git a/HandyControl/Controls/CoverView/CoverViewItem.cs b/HandyControl/Controls/CoverView/CoverViewItem.cs
index 986f59fc..f3ae61a5 100644
--- a/HandyControl/Controls/CoverView/CoverViewItem.cs
+++ b/HandyControl/Controls/CoverView/CoverViewItem.cs
@@ -1,58 +1,7 @@
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-using HandyControl.Data;
-
-namespace HandyControl.Controls
+namespace HandyControl.Controls
{
- public class CoverViewItem : HeaderedContentControl
- {
- private bool _isMouseLeftButtonDown;
-
- protected override void OnMouseLeave(MouseEventArgs e)
- {
- base.OnMouseLeave(e);
-
- _isMouseLeftButtonDown = false;
- }
-
- protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
- {
- base.OnMouseLeftButtonDown(e);
-
- _isMouseLeftButtonDown = true;
- }
-
- protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
- {
- base.OnMouseLeftButtonUp(e);
-
- if (_isMouseLeftButtonDown)
- {
- RaiseEvent(new RoutedEventArgs(SelectedEvent, this));
- _isMouseLeftButtonDown = false;
- }
- }
-
- public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
- "IsSelected", typeof(bool), typeof(CoverViewItem), new PropertyMetadata(ValueBoxes.FalseBox));
-
- public bool IsSelected
- {
- internal get => (bool) GetValue(IsSelectedProperty);
- set => SetValue(IsSelectedProperty, value);
- }
-
- public static readonly RoutedEvent SelectedEvent =
- EventManager.RegisterRoutedEvent("Selected", RoutingStrategy.Bubble,
- typeof(RoutedEventHandler), typeof(CoverViewItem));
-
- public event RoutedEventHandler Selected
- {
- add => AddHandler(SelectedEvent, value);
- remove => RemoveHandler(SelectedEvent, value);
- }
-
+ public class CoverViewItem : HeaderedSelectableItem
+ {
internal int Index { get; set; }
}
}
\ No newline at end of file
diff --git a/HandyControl/Controls/Rate/Rate.cs b/HandyControl/Controls/Rate/Rate.cs
index 95b9af84..907bb1d6 100644
--- a/HandyControl/Controls/Rate/Rate.cs
+++ b/HandyControl/Controls/Rate/Rate.cs
@@ -7,16 +7,13 @@ using HandyControl.Tools;
namespace HandyControl.Controls
{
- public class Rate : SimpleItemsControl
+ public class Rate : RegularItemsControl
{
- public static readonly DependencyProperty ItemMarginProperty = DependencyProperty.Register(
- "ItemMargin", typeof(Thickness), typeof(Rate), new PropertyMetadata(default(Thickness)));
-
- public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register(
- "ItemWidth", typeof(double), typeof(Rate), new PropertyMetadata(ValueBoxes.Double20Box));
-
- public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(
- "ItemHeight", typeof(double), typeof(Rate), new PropertyMetadata(ValueBoxes.Double20Box));
+ static Rate()
+ {
+ ItemWidthProperty.OverrideMetadata(typeof(Rate), new PropertyMetadata(ValueBoxes.Double20Box));
+ ItemHeightProperty.OverrideMetadata(typeof(Rate), new PropertyMetadata(ValueBoxes.Double20Box));
+ }
public static readonly DependencyProperty AllowHalfProperty = DependencyProperty.Register(
"AllowHalf", typeof(bool), typeof(Rate), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits));
@@ -42,6 +39,9 @@ namespace HandyControl.Controls
public static readonly DependencyProperty ShowTextProperty = DependencyProperty.Register(
"ShowText", typeof(bool), typeof(Rate), new PropertyMetadata(ValueBoxes.FalseBox));
+ public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
+ "IsReadOnly", typeof(bool), typeof(Rate), new PropertyMetadata(ValueBoxes.FalseBox));
+
private bool _isLoaded;
public Rate()
@@ -69,24 +69,6 @@ namespace HandyControl.Controls
};
}
- public Thickness ItemMargin
- {
- get => (Thickness) GetValue(ItemMarginProperty);
- set => SetValue(ItemMarginProperty, value);
- }
-
- public double ItemWidth
- {
- get => (double) GetValue(ItemWidthProperty);
- set => SetValue(ItemWidthProperty, value);
- }
-
- public double ItemHeight
- {
- get => (double) GetValue(ItemHeightProperty);
- set => SetValue(ItemHeightProperty, value);
- }
-
public bool AllowHalf
{
get => (bool) GetValue(AllowHalfProperty);
@@ -135,6 +117,12 @@ namespace HandyControl.Controls
set => SetValue(ShowTextProperty, value);
}
+ public bool IsReadOnly
+ {
+ get => (bool)GetValue(IsReadOnlyProperty);
+ set => SetValue(IsReadOnlyProperty, value);
+ }
+
private void RateItemValueChanged(object sender, RoutedEventArgs e)
{
Value =
@@ -201,9 +189,11 @@ namespace HandyControl.Controls
UpdateItems();
}
- private void UpdateItems()
+ protected override void UpdateItems()
{
+ if (!_isLoaded) return;
var count = (int) Value;
+
for (var i = 0; i < count; i++)
{
if (Items[i] is RateItem rateItem)
diff --git a/HandyControl/Controls/Rate/RateItem.cs b/HandyControl/Controls/Rate/RateItem.cs
index 3a297fb6..bdf2d878 100644
--- a/HandyControl/Controls/Rate/RateItem.cs
+++ b/HandyControl/Controls/Rate/RateItem.cs
@@ -20,6 +20,8 @@ namespace HandyControl.Controls
public static readonly DependencyProperty IconProperty = Rate.IconProperty.AddOwner(typeof(RateItem));
+ public static readonly DependencyProperty IsReadOnlyProperty = Rate.IsReadOnlyProperty.AddOwner(typeof(RateItem));
+
internal static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
"IsSelected", typeof(bool), typeof(RateItem),
new PropertyMetadata(ValueBoxes.FalseBox, OnIsSelectedChanged));
@@ -71,6 +73,12 @@ namespace HandyControl.Controls
set => SetValue(IsSelectedProperty, value);
}
+ public bool IsReadOnly
+ {
+ get => (bool)GetValue(IsReadOnlyProperty);
+ set => SetValue(IsReadOnlyProperty, value);
+ }
+
internal bool IsHalf
{
get => _isHalf;
@@ -123,6 +131,7 @@ namespace HandyControl.Controls
private void RateItem_MouseMove(object sender, MouseEventArgs e)
{
+ if (IsReadOnly) return;
if (!AllowHalf) return;
var p = e.GetPosition(this);
IsHalf = p.X < ActualWidth / 2;
@@ -145,7 +154,7 @@ namespace HandyControl.Controls
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
-
+ if (IsReadOnly) return;
_isSentValue = false;
IsSelected = true;
RaiseEvent(new RoutedEventArgs(SelectedChangedEvent) {Source = this});
@@ -154,21 +163,21 @@ namespace HandyControl.Controls
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
-
+ if (IsReadOnly) return;
_isMouseLeftButtonDown = true;
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
-
+ if (IsReadOnly) return;
_isMouseLeftButtonDown = false;
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
-
+ if (IsReadOnly) return;
if (_isMouseLeftButtonDown)
{
if (Index == 1 && AllowClear)
diff --git a/HandyControl/Controls/Window/BlurWindow.cs b/HandyControl/Controls/Window/BlurWindow.cs
index e9ccb82c..44582434 100644
--- a/HandyControl/Controls/Window/BlurWindow.cs
+++ b/HandyControl/Controls/Window/BlurWindow.cs
@@ -30,7 +30,7 @@ namespace HandyControl.Controls
}
else
{
- accentPolicy.AccentState = ExternDllHelper.ACCENTSTATE.ACCENT_ENABLE_TRANSPARENTGRADIENT;
+ return;
}
accentPolicy.AccentFlags = 2;
diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj
index 08d93472..77f6eba7 100644
--- a/HandyControl/HandyControl.csproj
+++ b/HandyControl/HandyControl.csproj
@@ -117,7 +117,13 @@
+
+
+
+
+
+
@@ -379,6 +385,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
@@ -415,6 +425,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
diff --git a/HandyControl/Themes/Styles/Base/CoverFlowBaseStyle.xaml b/HandyControl/Themes/Styles/Base/CoverFlowBaseStyle.xaml
new file mode 100644
index 00000000..e0817918
--- /dev/null
+++ b/HandyControl/Themes/Styles/Base/CoverFlowBaseStyle.xaml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/HandyControl/Themes/Styles/Base/ProgressBarBaseStyle.xaml b/HandyControl/Themes/Styles/Base/ProgressBarBaseStyle.xaml
index 5e96b5cb..1ac7de1f 100644
--- a/HandyControl/Themes/Styles/Base/ProgressBarBaseStyle.xaml
+++ b/HandyControl/Themes/Styles/Base/ProgressBarBaseStyle.xaml
@@ -83,7 +83,7 @@
-
+
diff --git a/HandyControl/Themes/Styles/Base/RateBaseStyle.xaml b/HandyControl/Themes/Styles/Base/RateBaseStyle.xaml
index 16835414..2d327fe2 100644
--- a/HandyControl/Themes/Styles/Base/RateBaseStyle.xaml
+++ b/HandyControl/Themes/Styles/Base/RateBaseStyle.xaml
@@ -14,6 +14,7 @@
+
diff --git a/HandyControl/Themes/Styles/CoverFlow.xaml b/HandyControl/Themes/Styles/CoverFlow.xaml
new file mode 100644
index 00000000..db772bff
--- /dev/null
+++ b/HandyControl/Themes/Styles/CoverFlow.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/HandyControl/Themes/Styles/Style.xaml b/HandyControl/Themes/Styles/Style.xaml
index a460151f..bf83dc78 100644
--- a/HandyControl/Themes/Styles/Style.xaml
+++ b/HandyControl/Themes/Styles/Style.xaml
@@ -51,6 +51,7 @@
+
\ No newline at end of file
diff --git a/HandyControl/Tools/Helper/ArithmeticHelper.cs b/HandyControl/Tools/Helper/ArithmeticHelper.cs
index 4ce4efdf..d6990e2b 100644
--- a/HandyControl/Tools/Helper/ArithmeticHelper.cs
+++ b/HandyControl/Tools/Helper/ArithmeticHelper.cs
@@ -2,6 +2,7 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
+using System.Windows.Media.Media3D;
namespace HandyControl.Tools
{
@@ -121,6 +122,11 @@ namespace HandyControl.Tools
///
public static double CalAngle(Point center, Point p) => Math.Atan2(p.Y - center.Y, p.X - center.X) * 180 / Math.PI;
+ ///
+ /// 判断是否是有效的双精度浮点数
+ ///
+ ///
+ ///
public static bool IsValidDoubleValue(object value)
{
var d = (double)value;
@@ -128,5 +134,19 @@ namespace HandyControl.Tools
return !double.IsInfinity(d);
return false;
}
+
+ ///
+ /// 计算法线
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Vector3D CalNormal(Point3D p0, Point3D p1, Point3D p2)
+ {
+ var v0 = new Vector3D(p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);
+ var v1 = new Vector3D(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);
+ return Vector3D.CrossProduct(v0, v1);
+ }
}
}
\ No newline at end of file
diff --git a/HandyControlDemo/Data/MessageToken.cs b/HandyControlDemo/Data/MessageToken.cs
index 927788fb..09a20af8 100644
--- a/HandyControlDemo/Data/MessageToken.cs
+++ b/HandyControlDemo/Data/MessageToken.cs
@@ -72,6 +72,8 @@
public static readonly string CoverViewDemoCtl = nameof(CoverViewDemoCtl);
+ public static readonly string CoverFlowDemoCtl = nameof(CoverFlowDemoCtl);
+
public static readonly string ButtonDemoCtl = nameof(ButtonDemoCtl);
public static readonly string ToggleButtonDemoCtl = nameof(ToggleButtonDemoCtl);
diff --git a/HandyControlDemo/Data/MessageToken.tt b/HandyControlDemo/Data/MessageToken.tt
index ffe9e0de..5453ceaf 100644
--- a/HandyControlDemo/Data/MessageToken.tt
+++ b/HandyControlDemo/Data/MessageToken.tt
@@ -38,7 +38,8 @@ var controlList = new List
"RateDemoCtl",
"ShieldDemoCtl",
"WaterfallPanelDemoCtl",
- "CoverViewDemoCtl"
+ "CoverViewDemoCtl",
+ "CoverFlowDemoCtl"
};
var styleList = new List
{
diff --git a/HandyControlDemo/HandyControlDemo.csproj b/HandyControlDemo/HandyControlDemo.csproj
index a051215e..bebcb158 100644
--- a/HandyControlDemo/HandyControlDemo.csproj
+++ b/HandyControlDemo/HandyControlDemo.csproj
@@ -139,6 +139,9 @@
CompareSliderDemoCtl.xaml
+
+ CoverFlowDemoCtl.xaml
+
CoverViewDemoCtl.xaml
@@ -421,6 +424,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
@@ -685,6 +692,7 @@
TextTemplatingFileGenerator
MessageToken.cs
+
diff --git a/HandyControlDemo/Properties/Langs/Lang.Designer.cs b/HandyControlDemo/Properties/Langs/Lang.Designer.cs
index 6e2798e8..717a2a83 100644
--- a/HandyControlDemo/Properties/Langs/Lang.Designer.cs
+++ b/HandyControlDemo/Properties/Langs/Lang.Designer.cs
@@ -47,7 +47,7 @@ namespace HandyControlDemo.Properties.Langs {
}
///
- /// 重写当前线程的 CurrentUICulture 属性
+ /// 使用此强类型资源类,为所有资源查找
/// 重写当前线程的 CurrentUICulture 属性。
///
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
@@ -303,6 +303,15 @@ namespace HandyControlDemo.Properties.Langs {
}
}
+ ///
+ /// 查找类似 封面流 的本地化字符串。
+ ///
+ public static string CoverFlow {
+ get {
+ return ResourceManager.GetString("CoverFlow", resourceCulture);
+ }
+ }
+
///
/// 查找类似 封面视图 的本地化字符串。
///
diff --git a/HandyControlDemo/Properties/Langs/Lang.en.resx b/HandyControlDemo/Properties/Langs/Lang.en.resx
index a99298ea..e236fd84 100644
--- a/HandyControlDemo/Properties/Langs/Lang.en.resx
+++ b/HandyControlDemo/Properties/Langs/Lang.en.resx
@@ -459,4 +459,7 @@
CoverView
+
+ CoverFlow
+
\ No newline at end of file
diff --git a/HandyControlDemo/Properties/Langs/Lang.resx b/HandyControlDemo/Properties/Langs/Lang.resx
index ef6c99fb..681ac894 100644
--- a/HandyControlDemo/Properties/Langs/Lang.resx
+++ b/HandyControlDemo/Properties/Langs/Lang.resx
@@ -439,4 +439,7 @@
封面视图
+
+ 封面流
+
\ No newline at end of file
diff --git a/HandyControlDemo/Resources/Img/LeftMainContent/LinearCarousel_16x.png b/HandyControlDemo/Resources/Img/LeftMainContent/LinearCarousel_16x.png
new file mode 100644
index 00000000..c52a21c2
Binary files /dev/null and b/HandyControlDemo/Resources/Img/LeftMainContent/LinearCarousel_16x.png differ
diff --git a/HandyControlDemo/UserControl/Controls/CoverFlowDemoCtl.xaml b/HandyControlDemo/UserControl/Controls/CoverFlowDemoCtl.xaml
new file mode 100644
index 00000000..bd203b9c
--- /dev/null
+++ b/HandyControlDemo/UserControl/Controls/CoverFlowDemoCtl.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/HandyControlDemo/UserControl/Controls/CoverFlowDemoCtl.xaml.cs b/HandyControlDemo/UserControl/Controls/CoverFlowDemoCtl.xaml.cs
new file mode 100644
index 00000000..dc020673
--- /dev/null
+++ b/HandyControlDemo/UserControl/Controls/CoverFlowDemoCtl.xaml.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+
+namespace HandyControlDemo.UserControl
+{
+ public partial class CoverFlowDemoCtl
+ {
+ public CoverFlowDemoCtl()
+ {
+ InitializeComponent();
+
+ CoverFlowMain.AddRange(new List
+ {
+ new Uri(@"pack://application:,,,/Resources/Img/Album/1.jpg"),
+ new Uri(@"pack://application:,,,/Resources/Img/Album/2.jpg"),
+ new Uri(@"pack://application:,,,/Resources/Img/Album/3.jpg"),
+ new Uri(@"pack://application:,,,/Resources/Img/Album/4.jpg"),
+ new Uri(@"pack://application:,,,/Resources/Img/Album/5.jpg"),
+ new Uri(@"pack://application:,,,/Resources/Img/Album/6.jpg"),
+ new Uri(@"pack://application:,,,/Resources/Img/Album/7.jpg"),
+ new Uri(@"pack://application:,,,/Resources/Img/Album/8.jpg"),
+ new Uri(@"pack://application:,,,/Resources/Img/Album/9.jpg"),
+ new Uri(@"pack://application:,,,/Resources/Img/Album/10.jpg")
+ });
+ CoverFlowMain.JumpTo(2);
+ }
+ }
+}
diff --git a/HandyControlDemo/UserControl/Controls/RateDemoCtl.xaml b/HandyControlDemo/UserControl/Controls/RateDemoCtl.xaml
index 1f7e3524..5f4cd7eb 100644
--- a/HandyControlDemo/UserControl/Controls/RateDemoCtl.xaml
+++ b/HandyControlDemo/UserControl/Controls/RateDemoCtl.xaml
@@ -7,7 +7,7 @@
-
+
diff --git a/HandyControlDemo/UserControl/Main/LeftMainContent.xaml b/HandyControlDemo/UserControl/Main/LeftMainContent.xaml
index 8137fa7c..0546e8db 100644
--- a/HandyControlDemo/UserControl/Main/LeftMainContent.xaml
+++ b/HandyControlDemo/UserControl/Main/LeftMainContent.xaml
@@ -190,7 +190,7 @@
-
+
@@ -240,6 +240,11 @@
+
+
+
+
+
@@ -310,7 +315,7 @@
-
+
diff --git a/README.md b/README.md
index 12f4b8d2..0cc43265 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,10 @@
## Latest examples
+### CoverFlow
+
+
+
### CoverView

@@ -22,12 +26,12 @@

+## History publication
+
### FlipClock

-## History publication
-
### Shield

diff --git a/Resources/CoverFlow.gif b/Resources/CoverFlow.gif
new file mode 100644
index 00000000..68045629
Binary files /dev/null and b/Resources/CoverFlow.gif differ