diff --git a/BPASmart.RecipeManagement/App.xaml b/BPASmart.RecipeManagement/App.xaml index 196cf0ec..3d9f99d4 100644 --- a/BPASmart.RecipeManagement/App.xaml +++ b/BPASmart.RecipeManagement/App.xaml @@ -87,7 +87,7 @@ - + diff --git a/BPASmartClient.MilkWithTea/App.xaml.cs b/BPASmartClient.MilkWithTea/App.xaml.cs index 266bb6c8..aba6737f 100644 --- a/BPASmartClient.MilkWithTea/App.xaml.cs +++ b/BPASmartClient.MilkWithTea/App.xaml.cs @@ -1,11 +1,22 @@ -using BPASmartClient.Helper; -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Data; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; +global using BPA.Message; +global using BPA.Message.Enum; +global using BPASmartClient.Device; +global using BPASmartClient.EventBus; +global using BPASmartClient.Helper; +global using BPASmartClient.MilkWithTea.Model; +global using BPASmartClient.Model; +global using BPASmartClient.MorkMOC; +global using System; +global using System.Collections.Generic; +global using System.Collections.ObjectModel; +global using System.Linq; +global using System.Text; +global using System.Threading.Tasks; +global using System.Windows; +global using System.Configuration; +global using System.Data; + + namespace BPASmartClient.MilkWithTea { diff --git a/BPASmartClient.MilkWithTea/BPASmartClient.MilkWithTea.csproj b/BPASmartClient.MilkWithTea/BPASmartClient.MilkWithTea.csproj index 43e4c5d7..7b248a82 100644 --- a/BPASmartClient.MilkWithTea/BPASmartClient.MilkWithTea.csproj +++ b/BPASmartClient.MilkWithTea/BPASmartClient.MilkWithTea.csproj @@ -23,14 +23,19 @@ + + + + + + - diff --git a/BPASmartClient.MilkWithTea/Control/ContextMenuToggleButton.cs b/BPASmartClient.MilkWithTea/Control/ContextMenuToggleButton.cs new file mode 100644 index 00000000..9ffe859b --- /dev/null +++ b/BPASmartClient.MilkWithTea/Control/ContextMenuToggleButton.cs @@ -0,0 +1,30 @@ +using System.Windows.Controls; +using System.Windows.Controls.Primitives; + + +namespace BPASmartClient.MilkWithTea.Control; + +/// +/// 带上下文菜单的切换按钮 +/// +public class ContextMenuToggleButton : ToggleButton +{ + public ContextMenu Menu { get; set; } + + protected override void OnClick() + { + base.OnClick(); + if (Menu != null) + { + if (IsChecked == true) + { + Menu.PlacementTarget = this; + Menu.IsOpen = true; + } + else + { + Menu.IsOpen = false; + } + } + } +} diff --git a/BPASmartClient.MilkWithTea/Control/ScrollViewer.cs b/BPASmartClient.MilkWithTea/Control/ScrollViewer.cs new file mode 100644 index 00000000..f289b71a --- /dev/null +++ b/BPASmartClient.MilkWithTea/Control/ScrollViewer.cs @@ -0,0 +1,225 @@ +using BPASmartClient.MilkWithTea.Data; +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace BPASmartClient.MilkWithTea.Control; + +public class ScrollViewer : System.Windows.Controls.ScrollViewer +{ + private double _totalVerticalOffset; + + private double _totalHorizontalOffset; + + private bool _isRunning; + + /// + /// 是否响应鼠标滚轮操作 + /// + public static readonly DependencyProperty CanMouseWheelProperty = DependencyProperty.Register( + "CanMouseWheel", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.TrueBox)); + + /// + /// 是否响应鼠标滚轮操作 + /// + public bool CanMouseWheel + { + get => (bool) GetValue(CanMouseWheelProperty); + set => SetValue(CanMouseWheelProperty, ValueBoxes.BooleanBox(value)); + } + + protected override void OnMouseWheel(MouseWheelEventArgs e) + { + if (!CanMouseWheel) return; + + if (!IsInertiaEnabled) + { + if (ScrollViewerAttach.GetOrientation(this) == Orientation.Vertical) + { + base.OnMouseWheel(e); + } + else + { + _totalHorizontalOffset = HorizontalOffset; + CurrentHorizontalOffset = HorizontalOffset; + _totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth); + CurrentHorizontalOffset = _totalHorizontalOffset; + } + return; + } + e.Handled = true; + + if (ScrollViewerAttach.GetOrientation(this) == Orientation.Vertical) + { + if (!_isRunning) + { + _totalVerticalOffset = VerticalOffset; + CurrentVerticalOffset = VerticalOffset; + } + _totalVerticalOffset = Math.Min(Math.Max(0, _totalVerticalOffset - e.Delta), ScrollableHeight); + ScrollToVerticalOffsetWithAnimation(_totalVerticalOffset); + } + else + { + if (!_isRunning) + { + _totalHorizontalOffset = HorizontalOffset; + CurrentHorizontalOffset = HorizontalOffset; + } + _totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth); + ScrollToHorizontalOffsetWithAnimation(_totalHorizontalOffset); + } + } + + internal void ScrollToTopInternal(double milliseconds = 500) + { + if (!_isRunning) + { + _totalVerticalOffset = VerticalOffset; + CurrentVerticalOffset = VerticalOffset; + } + ScrollToVerticalOffsetWithAnimation(0, milliseconds); + } + + public void ScrollToVerticalOffsetWithAnimation(double offset, double milliseconds = 500) + { + var animation = CreateAnimation(offset, milliseconds); + animation.EasingFunction = new CubicEase + { + EasingMode = EasingMode.EaseOut + }; + animation.FillBehavior = FillBehavior.Stop; + animation.Completed += (s, e1) => + { + CurrentVerticalOffset = offset; + _isRunning = false; + }; + _isRunning = true; + + BeginAnimation(CurrentVerticalOffsetProperty, animation, HandoffBehavior.Compose); + } + + public void ScrollToHorizontalOffsetWithAnimation(double offset, double milliseconds = 500) + { + var animation = CreateAnimation(offset, milliseconds); + animation.EasingFunction = new CubicEase + { + EasingMode = EasingMode.EaseOut + }; + animation.FillBehavior = FillBehavior.Stop; + animation.Completed += (s, e1) => + { + CurrentHorizontalOffset = offset; + _isRunning = false; + }; + _isRunning = true; + + BeginAnimation(CurrentHorizontalOffsetProperty, animation, HandoffBehavior.Compose); + } + + protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) => + IsPenetrating ? null : base.HitTestCore(hitTestParameters); + + /// + /// 是否支持惯性 + /// + public static readonly DependencyProperty IsInertiaEnabledProperty = DependencyProperty.RegisterAttached( + "IsInertiaEnabled", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox)); + + public static void SetIsInertiaEnabled(DependencyObject element, bool value) => element.SetValue(IsInertiaEnabledProperty, ValueBoxes.BooleanBox(value)); + + public static bool GetIsInertiaEnabled(DependencyObject element) => (bool) element.GetValue(IsInertiaEnabledProperty); + + /// + /// 是否支持惯性 + /// + public bool IsInertiaEnabled + { + get => (bool) GetValue(IsInertiaEnabledProperty); + set => SetValue(IsInertiaEnabledProperty, ValueBoxes.BooleanBox(value)); + } + + /// + /// 控件是否可以穿透点击 + /// + public static readonly DependencyProperty IsPenetratingProperty = DependencyProperty.RegisterAttached( + "IsPenetrating", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox)); + + /// + /// 控件是否可以穿透点击 + /// + public bool IsPenetrating + { + get => (bool) GetValue(IsPenetratingProperty); + set => SetValue(IsPenetratingProperty, ValueBoxes.BooleanBox(value)); + } + + public static void SetIsPenetrating(DependencyObject element, bool value) => element.SetValue(IsPenetratingProperty, ValueBoxes.BooleanBox(value)); + + public static bool GetIsPenetrating(DependencyObject element) => (bool) element.GetValue(IsPenetratingProperty); + + /// + /// 当前垂直滚动偏移 + /// + internal static readonly DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register( + "CurrentVerticalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.Double0Box, OnCurrentVerticalOffsetChanged)); + + private static void OnCurrentVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ScrollViewer ctl && e.NewValue is double v) + { + ctl.ScrollToVerticalOffset(v); + } + } + + /// + /// 当前垂直滚动偏移 + /// + internal double CurrentVerticalOffset + { + // ReSharper disable once UnusedMember.Local + get => (double) GetValue(CurrentVerticalOffsetProperty); + set => SetValue(CurrentVerticalOffsetProperty, value); + } + + /// + /// 当前水平滚动偏移 + /// + internal static readonly DependencyProperty CurrentHorizontalOffsetProperty = DependencyProperty.Register( + "CurrentHorizontalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.Double0Box, OnCurrentHorizontalOffsetChanged)); + + private static void OnCurrentHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ScrollViewer ctl && e.NewValue is double v) + { + ctl.ScrollToHorizontalOffset(v); + } + } + + /// + /// 当前水平滚动偏移 + /// + internal double CurrentHorizontalOffset + { + get => (double) GetValue(CurrentHorizontalOffsetProperty); + set => SetValue(CurrentHorizontalOffsetProperty, value); + } + + + /// + /// 创建一个Double动画 + /// + /// + /// + /// + public DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200) + { + return new(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds))) + { + EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut } + }; + } +} diff --git a/BPASmartClient.MilkWithTea/Control/ScrollViewerAttach.cs b/BPASmartClient.MilkWithTea/Control/ScrollViewerAttach.cs new file mode 100644 index 00000000..f943614b --- /dev/null +++ b/BPASmartClient.MilkWithTea/Control/ScrollViewerAttach.cs @@ -0,0 +1,111 @@ +using BPASmartClient.MilkWithTea.Data; +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace BPASmartClient.MilkWithTea.Control; + +public class ScrollViewerAttach +{ + public static readonly DependencyProperty AutoHideProperty = DependencyProperty.RegisterAttached( + "AutoHide", typeof(bool), typeof(ScrollViewerAttach), new FrameworkPropertyMetadata(ValueBoxes.TrueBox, FrameworkPropertyMetadataOptions.Inherits)); + + public static void SetAutoHide(DependencyObject element, bool value) + => element.SetValue(AutoHideProperty, ValueBoxes.BooleanBox(value)); + + public static bool GetAutoHide(DependencyObject element) + => (bool) element.GetValue(AutoHideProperty); + + public static readonly DependencyProperty OrientationProperty = DependencyProperty.RegisterAttached( + "Orientation", typeof(Orientation), typeof(ScrollViewerAttach), new FrameworkPropertyMetadata(ValueBoxes.VerticalBox, FrameworkPropertyMetadataOptions.Inherits, OnOrientationChanged)); + + private static void OnOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is ScrollViewer) + { + return; + } + + if (d is System.Windows.Controls.ScrollViewer scrollViewer) + { + if ((Orientation) e.NewValue == Orientation.Horizontal) + { + scrollViewer.PreviewMouseWheel += ScrollViewerPreviewMouseWheel; + } + else + { + scrollViewer.PreviewMouseWheel -= ScrollViewerPreviewMouseWheel; + } + } + + void ScrollViewerPreviewMouseWheel(object sender, MouseWheelEventArgs args) + { + var scrollViewerNative = (System.Windows.Controls.ScrollViewer) sender; + scrollViewerNative.ScrollToHorizontalOffset(Math.Min(Math.Max(0, scrollViewerNative.HorizontalOffset - args.Delta), scrollViewerNative.ScrollableWidth)); + + args.Handled = true; + } + } + + public static void SetOrientation(DependencyObject element, Orientation value) + => element.SetValue(OrientationProperty, ValueBoxes.OrientationBox(value)); + + public static Orientation GetOrientation(DependencyObject element) + => (Orientation) element.GetValue(OrientationProperty); + + public static readonly DependencyProperty IsDisabledProperty = DependencyProperty.RegisterAttached( + "IsDisabled", typeof(bool), typeof(ScrollViewerAttach), new PropertyMetadata(ValueBoxes.FalseBox, OnIsDisabledChanged)); + + private static void OnIsDisabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is UIElement element) + { + if ((bool) e.NewValue) + { + element.PreviewMouseWheel += ScrollViewerPreviewMouseWheel; + } + else + { + element.PreviewMouseWheel -= ScrollViewerPreviewMouseWheel; + } + } + + void ScrollViewerPreviewMouseWheel(object sender, MouseWheelEventArgs args) + { + if (args.Handled) + { + return; + } + + args.Handled = true; + + if (GetParent((UIElement) sender) is { } scrollViewer) + { + scrollViewer.RaiseEvent(new MouseWheelEventArgs(args.MouseDevice, args.Timestamp, args.Delta) + { + RoutedEvent = UIElement.MouseWheelEvent, + Source = sender + }); + } + } + + static T GetParent(DependencyObject d) where T : DependencyObject => + d switch + { + null => default, + T t => t, + Window _ => null, + _ => GetParent(VisualTreeHelper.GetParent(d)) + }; + } + + public static void SetIsDisabled(DependencyObject element, bool value) + => element.SetValue(IsDisabledProperty, ValueBoxes.BooleanBox(value)); + + public static bool GetIsDisabled(DependencyObject element) + => (bool) element.GetValue(IsDisabledProperty); + + +} diff --git a/BPASmartClient.MilkWithTea/Control/TabControl.cs b/BPASmartClient.MilkWithTea/Control/TabControl.cs new file mode 100644 index 00000000..342f4127 --- /dev/null +++ b/BPASmartClient.MilkWithTea/Control/TabControl.cs @@ -0,0 +1,426 @@ +using BPASmartClient.MilkWithTea.Data; +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; + +namespace BPASmartClient.MilkWithTea.Control; + +[TemplatePart(Name = HeaderPanelKey, Type = typeof(TabPanel))] +[TemplatePart(Name = OverflowScrollviewer, Type = typeof(ScrollViewer))] +[TemplatePart(Name = ScrollButtonLeft, Type = typeof(ButtonBase))] +[TemplatePart(Name = ScrollButtonRight, Type = typeof(ButtonBase))] +[TemplatePart(Name = HeaderBorder, Type = typeof(Border))] +public class TabControl : System.Windows.Controls.TabControl +{ + private const string OverflowButtonKey = "PART_OverflowButton"; + + private const string HeaderPanelKey = "PART_HeaderPanel"; + + private const string OverflowScrollviewer = "PART_OverflowScrollviewer"; + + private const string ScrollButtonLeft = "PART_ScrollButtonLeft"; + + private const string ScrollButtonRight = "PART_ScrollButtonRight"; + + private const string HeaderBorder = "PART_HeaderBorder"; + + private ContextMenuToggleButton _buttonOverflow; + + internal TabPanel HeaderPanel { get; private set; } + + private ScrollViewer _scrollViewerOverflow; + + private ButtonBase _buttonScrollLeft; + + private ButtonBase _buttonScrollRight; + + private Border _headerBorder; + + /// + /// 是否为内部操作 + /// + internal bool IsInternalAction; + + /// + /// 是否启用动画 + /// + public static readonly DependencyProperty IsAnimationEnabledProperty = DependencyProperty.Register( + "IsAnimationEnabled", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); + + /// + /// 是否启用动画 + /// + public bool IsAnimationEnabled + { + get => (bool) GetValue(IsAnimationEnabledProperty); + set => SetValue(IsAnimationEnabledProperty, ValueBoxes.BooleanBox(value)); + } + + /// + /// 是否可以拖动 + /// + public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.Register( + "IsDraggable", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); + + /// + /// 是否可以拖动 + /// + public bool IsDraggable + { + get => (bool) GetValue(IsDraggableProperty); + set => SetValue(IsDraggableProperty, ValueBoxes.BooleanBox(value)); + } + + /// + /// 是否显示关闭按钮 + /// + public static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.RegisterAttached( + "ShowCloseButton", typeof(bool), typeof(TabControl), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits)); + + public static void SetShowCloseButton(DependencyObject element, bool value) + => element.SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value)); + + public static bool GetShowCloseButton(DependencyObject element) + => (bool) element.GetValue(ShowCloseButtonProperty); + + /// + /// 是否显示关闭按钮 + /// + public bool ShowCloseButton + { + get => (bool) GetValue(ShowCloseButtonProperty); + set => SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value)); + } + + /// + /// 是否显示上下文菜单 + /// + public static readonly DependencyProperty ShowContextMenuProperty = DependencyProperty.RegisterAttached( + "ShowContextMenu", typeof(bool), typeof(TabControl), new FrameworkPropertyMetadata(ValueBoxes.TrueBox, FrameworkPropertyMetadataOptions.Inherits)); + + public static void SetShowContextMenu(DependencyObject element, bool value) + => element.SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value)); + + public static bool GetShowContextMenu(DependencyObject element) + => (bool) element.GetValue(ShowContextMenuProperty); + + /// + /// 是否显示上下文菜单 + /// + public bool ShowContextMenu + { + get => (bool) GetValue(ShowContextMenuProperty); + set => SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value)); + } + + /// + /// 是否将标签填充 + /// + public static readonly DependencyProperty IsTabFillEnabledProperty = DependencyProperty.Register( + "IsTabFillEnabled", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); + + /// + /// 是否将标签填充 + /// + public bool IsTabFillEnabled + { + get => (bool) GetValue(IsTabFillEnabledProperty); + set => SetValue(IsTabFillEnabledProperty, ValueBoxes.BooleanBox(value)); + } + + /// + /// 标签宽度 + /// + public static readonly DependencyProperty TabItemWidthProperty = DependencyProperty.Register( + "TabItemWidth", typeof(double), typeof(TabControl), new PropertyMetadata(200.0)); + + /// + /// 标签宽度 + /// + public double TabItemWidth + { + get => (double) GetValue(TabItemWidthProperty); + set => SetValue(TabItemWidthProperty, value); + } + + /// + /// 标签高度 + /// + public static readonly DependencyProperty TabItemHeightProperty = DependencyProperty.Register( + "TabItemHeight", typeof(double), typeof(TabControl), new PropertyMetadata(30.0)); + + /// + /// 标签高度 + /// + public double TabItemHeight + { + get => (double) GetValue(TabItemHeightProperty); + set => SetValue(TabItemHeightProperty, value); + } + + /// + /// 是否可以滚动 + /// + public static readonly DependencyProperty IsScrollableProperty = DependencyProperty.Register( + "IsScrollable", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); + + /// + /// 是否可以滚动 + /// + public bool IsScrollable + { + get => (bool) GetValue(IsScrollableProperty); + set => SetValue(IsScrollableProperty, ValueBoxes.BooleanBox(value)); + } + + /// + /// 是否显示溢出按钮 + /// + public static readonly DependencyProperty ShowOverflowButtonProperty = DependencyProperty.Register( + "ShowOverflowButton", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.TrueBox)); + + /// + /// 是否显示溢出按钮 + /// + public bool ShowOverflowButton + { + get => (bool) GetValue(ShowOverflowButtonProperty); + set => SetValue(ShowOverflowButtonProperty, ValueBoxes.BooleanBox(value)); + } + + /// + /// 是否显示滚动按钮 + /// + public static readonly DependencyProperty ShowScrollButtonProperty = DependencyProperty.Register( + "ShowScrollButton", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox)); + + /// + /// 是否显示滚动按钮 + /// + public bool ShowScrollButton + { + get => (bool) GetValue(ShowScrollButtonProperty); + set => SetValue(ShowScrollButtonProperty, ValueBoxes.BooleanBox(value)); + } + + /// + /// 可见的标签数量 + /// + private int _itemShowCount; + + protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) + { + base.OnItemsChanged(e); + + if (HeaderPanel == null) + { + IsInternalAction = false; + return; + } + + UpdateOverflowButton(); + + if (IsInternalAction) + { + IsInternalAction = false; + return; + } + + if (e.Action == NotifyCollectionChangedAction.Add) + { + for (var i = 0; i < Items.Count; i++) + { + if (ItemContainerGenerator.ContainerFromIndex(i) is not TabItem item) return; + item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + item.TabPanel = HeaderPanel; + } + } + + _headerBorder?.InvalidateMeasure(); + IsInternalAction = false; + } + + public override void OnApplyTemplate() + { + if (_buttonOverflow != null) + { + if (_buttonOverflow.Menu != null) + { + _buttonOverflow.Menu.Closed -= Menu_Closed; + _buttonOverflow.Menu = null; + } + + _buttonOverflow.Click -= ButtonOverflow_Click; + } + + if (_buttonScrollLeft != null) _buttonScrollLeft.Click -= ButtonScrollLeft_Click; + if (_buttonScrollRight != null) _buttonScrollRight.Click -= ButtonScrollRight_Click; + + base.OnApplyTemplate(); + HeaderPanel = GetTemplateChild(HeaderPanelKey) as TabPanel; + + if (IsTabFillEnabled) return; + + _buttonOverflow = GetTemplateChild(OverflowButtonKey) as ContextMenuToggleButton; + _scrollViewerOverflow = GetTemplateChild(OverflowScrollviewer) as ScrollViewer; + _buttonScrollLeft = GetTemplateChild(ScrollButtonLeft) as ButtonBase; + _buttonScrollRight = GetTemplateChild(ScrollButtonRight) as ButtonBase; + _headerBorder = GetTemplateChild(HeaderBorder) as Border; + + if (_buttonScrollLeft != null) _buttonScrollLeft.Click += ButtonScrollLeft_Click; + if (_buttonScrollRight != null) _buttonScrollRight.Click += ButtonScrollRight_Click; + + if (_buttonOverflow != null) + { + var menu = new ContextMenu + { + Placement = PlacementMode.Bottom, + PlacementTarget = _buttonOverflow + }; + menu.Closed += Menu_Closed; + _buttonOverflow.Menu = menu; + _buttonOverflow.Click += ButtonOverflow_Click; + } + } + + protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) + { + base.OnRenderSizeChanged(sizeInfo); + UpdateOverflowButton(); + } + + private void UpdateOverflowButton() + { + if (!IsTabFillEnabled) + { + _itemShowCount = (int) (ActualWidth / TabItemWidth); + //_buttonOverflow?.Show(ShowOverflowButton && Items.Count > 0 && Items.Count >= _itemShowCount); + } + } + + private void Menu_Closed(object sender, RoutedEventArgs e) => _buttonOverflow.IsChecked = false; + + private void ButtonScrollRight_Click(object sender, RoutedEventArgs e) => + _scrollViewerOverflow.ScrollToHorizontalOffsetWithAnimation(Math.Min( + _scrollViewerOverflow.CurrentHorizontalOffset + TabItemWidth, _scrollViewerOverflow.ScrollableWidth)); + + private void ButtonScrollLeft_Click(object sender, RoutedEventArgs e) => + _scrollViewerOverflow.ScrollToHorizontalOffsetWithAnimation(Math.Max( + _scrollViewerOverflow.CurrentHorizontalOffset - TabItemWidth, 0)); + + private void ButtonOverflow_Click(object sender, RoutedEventArgs e) + { + if (_buttonOverflow.IsChecked == true) + { + _buttonOverflow.Menu.Items.Clear(); + for (var i = 0; i < Items.Count; i++) + { + if (ItemContainerGenerator.ContainerFromIndex(i) is not TabItem item) continue; + + var menuItem = new MenuItem + { + HeaderStringFormat = ItemStringFormat, + HeaderTemplate = ItemTemplate, + HeaderTemplateSelector = ItemTemplateSelector, + Header = item.Header, + Width = TabItemWidth, + IsChecked = item.IsSelected, + IsCheckable = true, + IsEnabled = item.IsEnabled + }; + + menuItem.Click += delegate + { + _buttonOverflow.IsChecked = false; + + var list = GetActualList(); + if (list == null) return; + + var actualItem = ItemContainerGenerator.ItemFromContainer(item); + if (actualItem == null) return; + + var index = list.IndexOf(actualItem); + if (index >= _itemShowCount) + { + list.Remove(actualItem); + list.Insert(0, actualItem); + HeaderPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, + IsAnimationEnabled + ? new Duration(TimeSpan.FromMilliseconds(200)) + : new Duration(TimeSpan.FromMilliseconds(0))); + HeaderPanel.ForceUpdate = true; + HeaderPanel.Measure(new Size(HeaderPanel.DesiredSize.Width, ActualHeight)); + HeaderPanel.ForceUpdate = false; + SetCurrentValue(SelectedIndexProperty, ValueBoxes.Int0Box); + } + + item.IsSelected = true; + }; + _buttonOverflow.Menu.Items.Add(menuItem); + } + } + } + + internal double GetHorizontalOffset() => _scrollViewerOverflow?.CurrentHorizontalOffset ?? 0; + + internal void UpdateScroll() => _scrollViewerOverflow?.RaiseEvent(new MouseWheelEventArgs(Mouse.PrimaryDevice, Environment.TickCount, 0) + { + RoutedEvent = MouseWheelEvent + }); + + internal void CloseAllItems() => CloseOtherItems(null); + + internal void CloseOtherItems(TabItem currentItem) + { + var actualItem = currentItem != null ? ItemContainerGenerator.ItemFromContainer(currentItem) : null; + + var list = GetActualList(); + if (list == null) return; + + IsInternalAction = true; + + for (var i = 0; i < Items.Count; i++) + { + var item = list[i]; + if (!Equals(item, actualItem) && item != null) + { + var argsClosing = new CancelRoutedEventArgs(TabItem.ClosingEvent, item); + + if (ItemContainerGenerator.ContainerFromItem(item) is not TabItem tabItem) continue; + + tabItem.RaiseEvent(argsClosing); + if (argsClosing.Cancel) return; + + tabItem.RaiseEvent(new RoutedEventArgs(TabItem.ClosedEvent, item)); + list.Remove(item); + + i--; + } + } + + SetCurrentValue(SelectedIndexProperty, Items.Count == 0 ? -1 : 0); + } + + internal IList GetActualList() + { + IList list; + if (ItemsSource != null) + { + list = ItemsSource as IList; + } + else + { + list = Items; + } + + return list; + } + + protected override bool IsItemItsOwnContainerOverride(object item) => item is TabItem; + + protected override DependencyObject GetContainerForItemOverride() => new TabItem(); +} diff --git a/BPASmartClient.MilkWithTea/Control/TabItem.cs b/BPASmartClient.MilkWithTea/Control/TabItem.cs new file mode 100644 index 00000000..d911285e --- /dev/null +++ b/BPASmartClient.MilkWithTea/Control/TabItem.cs @@ -0,0 +1,509 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using BPASmartClient.MilkWithTea.Control; +using BPASmartClient.MilkWithTea.Data; +using TabControl = BPASmartClient.MilkWithTea.Control.TabControl; + +namespace BPASmartClient.MilkWithTea.Control; + +public class TabItem : System.Windows.Controls.TabItem +{ + /// + /// 动画速度 + /// + private const int AnimationSpeed = 150; + + /// + /// 选项卡是否处于拖动状态 + /// + private static bool ItemIsDragging; + + /// + /// 选项卡是否等待被拖动 + /// + private bool _isWaiting; + + /// + /// 拖动中的选项卡坐标 + /// + private Point _dragPoint; + + /// + /// 鼠标按下时选项卡位置 + /// + private int _mouseDownIndex; + + /// + /// 鼠标按下时选项卡横向偏移 + /// + private double _mouseDownOffsetX; + + /// + /// 鼠标按下时的坐标 + /// + private Point _mouseDownPoint; + + /// + /// 右侧可移动的最大值 + /// + private double _maxMoveRight; + + /// + /// 左侧可移动的最大值 + /// + private double _maxMoveLeft; + + /// + /// 选项卡宽度 + /// + public double ItemWidth { get; internal set; } + + /// + /// 选项卡拖动等待距离(在鼠标移动了超过20个像素无关单位后,选项卡才开始被拖动) + /// + private const double WaitLength = 20; + + /// + /// 选项卡是否处于拖动状态 + /// + private bool _isDragging; + + /// + /// 选项卡是否已经被拖动 + /// + private bool _isDragged; + + /// + /// 目标横向位移 + /// + internal double TargetOffsetX { get; set; } + + /// + /// 当前编号 + /// + private int _currentIndex; + + /// + /// 标签容器横向滚动距离 + /// + private double _scrollHorizontalOffset; + + + private TabPanel _tabPanel; + + + /// + /// 标签容器 + /// + internal TabPanel TabPanel + { + get + { + if (_tabPanel == null && TabControlParent != null) + { + _tabPanel = TabControlParent.HeaderPanel; + } + + return _tabPanel; + } + set => _tabPanel = value; + } + + private TabControl TabControlParent => ItemsControl.ItemsControlFromItemContainer(this) as TabControl; + + + /// + /// 当前编号 + /// + internal int CurrentIndex + { + get => _currentIndex; + set + { + if (_currentIndex == value || value < 0) return; + var oldIndex = _currentIndex; + _currentIndex = value; + UpdateItemOffsetX(oldIndex); + } + } + + /// + /// 是否显示关闭按钮 + /// + public static readonly DependencyProperty ShowCloseButtonProperty = + TabControl.ShowCloseButtonProperty.AddOwner(typeof(TabItem)); + + /// + /// 是否显示关闭按钮 + /// + public bool ShowCloseButton + { + get => (bool) GetValue(ShowCloseButtonProperty); + set => SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value)); + } + + public static void SetShowCloseButton(DependencyObject element, bool value) + => element.SetValue(ShowCloseButtonProperty, ValueBoxes.BooleanBox(value)); + + public static bool GetShowCloseButton(DependencyObject element) + => (bool) element.GetValue(ShowCloseButtonProperty); + + /// + /// 是否显示上下文菜单 + /// + public static readonly DependencyProperty ShowContextMenuProperty = + TabControl.ShowContextMenuProperty.AddOwner(typeof(TabItem), new FrameworkPropertyMetadata(OnShowContextMenuChanged)); + + private static void OnShowContextMenuChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var ctl = (TabItem) d; + if (ctl.Menu != null) + { + var show = (bool) e.NewValue; + ctl.Menu.IsEnabled = show; + //ctl.Menu.Show(show); + } + } + + /// + /// 是否显示上下文菜单 + /// + public bool ShowContextMenu + { + get => (bool) GetValue(ShowContextMenuProperty); + set => SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value)); + } + + public static void SetShowContextMenu(DependencyObject element, bool value) + => element.SetValue(ShowContextMenuProperty, ValueBoxes.BooleanBox(value)); + + public static bool GetShowContextMenu(DependencyObject element) + => (bool) element.GetValue(ShowContextMenuProperty); + + public static readonly DependencyProperty MenuProperty = DependencyProperty.Register( + "Menu", typeof(ContextMenu), typeof(TabItem), new PropertyMetadata(default(ContextMenu), OnMenuChanged)); + + private static void OnMenuChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var ctl = (TabItem) d; + //ctl.OnMenuChanged(e.NewValue as ContextMenu); + } + + //private void OnMenuChanged(ContextMenu menu) + //{ + // if (IsLoaded && menu != null) + // { + // var parent = TabControlParent; + // if (parent == null) return; + + // var item = parent.ItemContainerGenerator.ItemFromContainer(this); + + // menu.DataContext = item; + // menu.SetBinding(IsEnabledProperty, new Binding(ShowContextMenuProperty.Name) + // { + // Source = this + // }); + // menu.SetBinding(VisibilityProperty, new Binding(ShowContextMenuProperty.Name) + // { + // Source = this, + // Converter = GetResourceInternal(ResourceToken.Boolean2VisibilityConverter) + // }); + // } + //} + + + + public ContextMenu Menu + { + get => (ContextMenu) GetValue(MenuProperty); + set => SetValue(MenuProperty, value); + } + + /// + /// 更新选项卡横向偏移 + /// + /// + private void UpdateItemOffsetX(int oldIndex) + { + if (!_isDragging || CurrentIndex >= TabPanel.ItemDic.Count) + { + return; + } + + var moveItem = TabPanel.ItemDic[CurrentIndex]; + moveItem.CurrentIndex -= CurrentIndex - oldIndex; + var offsetX = moveItem.TargetOffsetX; + var resultX = offsetX + (oldIndex - CurrentIndex) * ItemWidth; + TabPanel.ItemDic[CurrentIndex] = this; + TabPanel.ItemDic[moveItem.CurrentIndex] = moveItem; + moveItem.CreateAnimation(offsetX, resultX); + } + + public TabItem() + { + //CommandBindings.Add(new CommandBinding(ControlCommands.Close, (s, e) => Close())); + //CommandBindings.Add(new CommandBinding(ControlCommands.CloseAll, + // (s, e) => { TabControlParent.CloseAllItems(); })); + //CommandBindings.Add(new CommandBinding(ControlCommands.CloseOther, + // (s, e) => { TabControlParent.CloseOtherItems(this); })); + + //Loaded += (s, e) => OnMenuChanged(Menu); + } + + + protected override void OnMouseRightButtonDown(MouseButtonEventArgs e) + { + base.OnMouseRightButtonDown(e); + + if (VisualTreeHelper.HitTest(this, e.GetPosition(this)) == null) return; + IsSelected = true; + Focus(); + } + + protected override void OnHeaderChanged(object oldHeader, object newHeader) + { + base.OnHeaderChanged(oldHeader, newHeader); + + if (TabPanel != null) + { + TabPanel.ForceUpdate = true; + InvalidateMeasure(); + TabPanel.ForceUpdate = true; + } + } + + internal void Close() + { + var parent = TabControlParent; + if (parent == null) return; + + var item = parent.ItemContainerGenerator.ItemFromContainer(this); + + var argsClosing = new CancelRoutedEventArgs(ClosingEvent, item); + RaiseEvent(argsClosing); + if (argsClosing.Cancel) return; + + TabPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, parent.IsAnimationEnabled + ? new Duration(TimeSpan.FromMilliseconds(200)) + : new Duration(TimeSpan.FromMilliseconds(1))); + + parent.IsInternalAction = true; + RaiseEvent(new RoutedEventArgs(ClosedEvent, item)); + + var list = parent.GetActualList(); + list?.Remove(item); + } + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + + if (VisualTreeHelper.HitTest(this, e.GetPosition(this)) == null) return; + var parent = TabControlParent; + if (parent == null) return; + + if (parent.IsDraggable && !ItemIsDragging && !_isDragging) + { + parent.UpdateScroll(); + TabPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromSeconds(0))); + _mouseDownOffsetX = RenderTransform.Value.OffsetX; + _scrollHorizontalOffset = parent.GetHorizontalOffset(); + var mx = TranslatePoint(new Point(), parent).X + _scrollHorizontalOffset; + _mouseDownIndex = CalLocationIndex(mx); + var subIndex = _mouseDownIndex - CalLocationIndex(_scrollHorizontalOffset); + _maxMoveLeft = -subIndex * ItemWidth; + _maxMoveRight = parent.ActualWidth - ActualWidth + _maxMoveLeft; + + _isDragging = true; + ItemIsDragging = true; + _isWaiting = true; + _dragPoint = e.GetPosition(parent); + _dragPoint = new Point(_dragPoint.X + _scrollHorizontalOffset, _dragPoint.Y); + _mouseDownPoint = _dragPoint; + CaptureMouse(); + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (ItemIsDragging && _isDragging) + { + var parent = TabControlParent; + if (parent == null) return; + + var subX = TranslatePoint(new Point(), parent).X + _scrollHorizontalOffset; + CurrentIndex = CalLocationIndex(subX); + + var p = e.GetPosition(parent); + p = new Point(p.X + _scrollHorizontalOffset, p.Y); + + var subLeft = p.X - _dragPoint.X; + var totalLeft = p.X - _mouseDownPoint.X; + + if (Math.Abs(subLeft) <= WaitLength && _isWaiting) return; + + _isWaiting = false; + _isDragged = true; + + var left = subLeft + RenderTransform.Value.OffsetX; + if (totalLeft < _maxMoveLeft) + { + left = _maxMoveLeft + _mouseDownOffsetX; + } + else if (totalLeft > _maxMoveRight) + { + left = _maxMoveRight + _mouseDownOffsetX; + } + + var t = new TranslateTransform(left, 0); + RenderTransform = t; + _dragPoint = p; + } + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonUp(e); + + ReleaseMouseCapture(); + + if (_isDragged) + { + var parent = TabControlParent; + if (parent == null) return; + + var subX = TranslatePoint(new Point(), parent).X + _scrollHorizontalOffset; + var index = CalLocationIndex(subX); + var left = index * ItemWidth; + var offsetX = RenderTransform.Value.OffsetX; + CreateAnimation(offsetX, offsetX - subX + left, index); + } + + _isDragging = false; + ItemIsDragging = false; + _isDragged = false; + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Pressed) + { + if (ShowCloseButton || ShowContextMenu) + { + Close(); + } + } + } + + /// + /// 创建动画 + /// + internal void CreateAnimation(double offsetX, double resultX, int index = -1) + { + var parent = TabControlParent; + + void AnimationCompleted() + { + RenderTransform = new TranslateTransform(resultX, 0); + if (index == -1) return; + + var list = parent.GetActualList(); + if (list == null) return; + + var item = parent.ItemContainerGenerator.ItemFromContainer(this); + if (item == null) return; + + TabPanel.CanUpdate = false; + parent.IsInternalAction = true; + + list.Remove(item); + parent.IsInternalAction = true; + list.Insert(index, item); + _tabPanel.SetValue(TabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromMilliseconds(0))); + TabPanel.CanUpdate = true; + TabPanel.ForceUpdate = true; + TabPanel.Measure(new Size(TabPanel.DesiredSize.Width, ActualHeight)); + TabPanel.ForceUpdate = false; + + Focus(); + IsSelected = true; + + if (!IsMouseCaptured) + { + parent.SetCurrentValue(Selector.SelectedIndexProperty, _currentIndex); + } + } + + TargetOffsetX = resultX; + if (!parent.IsAnimationEnabled) + { + AnimationCompleted(); + return; + } + + var animation = CreateAnimation(resultX, AnimationSpeed); + animation.FillBehavior = FillBehavior.Stop; + animation.Completed += (s1, e1) => AnimationCompleted(); + var f = new TranslateTransform(offsetX, 0); + RenderTransform = f; + f.BeginAnimation(TranslateTransform.XProperty, animation, HandoffBehavior.Compose); + } + /// + /// 创建一个Double动画 + /// + /// + /// + /// + public static DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200) + { + return new(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds))) + { + EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut } + }; + } + + + /// + /// 计算选项卡当前合适的位置编号 + /// + /// + /// + private int CalLocationIndex(double left) + { + if (_isWaiting) + { + return CurrentIndex; + } + + var maxIndex = TabControlParent.Items.Count - 1; + var div = (int) (left / ItemWidth); + var rest = left % ItemWidth; + var result = rest / ItemWidth > .5 ? div + 1 : div; + + return result > maxIndex ? maxIndex : result; + } + + public static readonly RoutedEvent ClosingEvent = EventManager.RegisterRoutedEvent("Closing", RoutingStrategy.Bubble, typeof(EventHandler), typeof(TabItem)); + + public event EventHandler Closing + { + add => AddHandler(ClosingEvent, value); + remove => RemoveHandler(ClosingEvent, value); + } + + public static readonly RoutedEvent ClosedEvent = EventManager.RegisterRoutedEvent("Closed", RoutingStrategy.Bubble, typeof(EventHandler), typeof(TabItem)); + + public event EventHandler Closed + { + add => AddHandler(ClosedEvent, value); + remove => RemoveHandler(ClosedEvent, value); + } +} diff --git a/BPASmartClient.MilkWithTea/Control/TabPanel.cs b/BPASmartClient.MilkWithTea/Control/TabPanel.cs new file mode 100644 index 00000000..82efc317 --- /dev/null +++ b/BPASmartClient.MilkWithTea/Control/TabPanel.cs @@ -0,0 +1,200 @@ +using BPASmartClient.MilkWithTea.Data; +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + + +namespace BPASmartClient.MilkWithTea.Control; + +public class TabPanel : Panel +{ + private int _itemCount; + + /// + /// 是否可以更新 + /// + internal bool CanUpdate = true; + + /// + /// 选项卡字典 + /// + internal Dictionary ItemDic = new(); + + public static readonly DependencyPropertyKey FluidMoveDurationPropertyKey = + DependencyProperty.RegisterReadOnly("FluidMoveDuration", typeof(Duration), typeof(TabPanel), + new PropertyMetadata(new Duration(TimeSpan.FromMilliseconds(0)))); + + /// + /// 流式行为持续时间 + /// + public static readonly DependencyProperty FluidMoveDurationProperty = + FluidMoveDurationPropertyKey.DependencyProperty; + + /// + /// 流式行为持续时间 + /// + public Duration FluidMoveDuration + { + get => (Duration) GetValue(FluidMoveDurationProperty); + set => SetValue(FluidMoveDurationProperty, value); + } + + /// + /// 是否将标签填充 + /// + public static readonly DependencyProperty IsTabFillEnabledProperty = DependencyProperty.Register( + "IsTabFillEnabled", typeof(bool), typeof(TabPanel), new PropertyMetadata(ValueBoxes.FalseBox)); + + /// + /// 是否将标签填充 + /// + public bool IsTabFillEnabled + { + get => (bool) GetValue(IsTabFillEnabledProperty); + set => SetValue(IsTabFillEnabledProperty, ValueBoxes.BooleanBox(value)); + } + + /// + /// 标签宽度 + /// + public static readonly DependencyProperty TabItemWidthProperty = DependencyProperty.Register( + "TabItemWidth", typeof(double), typeof(TabPanel), new PropertyMetadata(200.0)); + + /// + /// 标签宽度 + /// + public double TabItemWidth + { + get => (double) GetValue(TabItemWidthProperty); + set => SetValue(TabItemWidthProperty, value); + } + + /// + /// 标签高度 + /// + public static readonly DependencyProperty TabItemHeightProperty = DependencyProperty.Register( + "TabItemHeight", typeof(double), typeof(TabPanel), new PropertyMetadata(30.0)); + + /// + /// 标签高度 + /// + public double TabItemHeight + { + get => (double) GetValue(TabItemHeightProperty); + set => SetValue(TabItemHeightProperty, value); + } + + /// + /// 是否可以强制更新 + /// + internal bool ForceUpdate; + + private Size _oldSize; + + /// + /// 是否已经加载 + /// + private bool _isLoaded; + + protected override Size MeasureOverride(Size constraint) + { + if ((_itemCount == InternalChildren.Count || !CanUpdate) && !ForceUpdate && !IsTabFillEnabled) return _oldSize; + constraint.Height = TabItemHeight; + _itemCount = InternalChildren.Count; + + var size = new Size(); + + ItemDic.Clear(); + + var count = InternalChildren.Count; + if (count == 0) + { + _oldSize = new Size(); + return _oldSize; + } + constraint.Width += InternalChildren.Count; + + var itemWidth = .0; + var arr = new int[count]; + + if (!IsTabFillEnabled) + { + itemWidth = TabItemWidth; + } + else + { + if (TemplatedParent is TabControl tabControl) + { + arr = DivideInt2Arr((int) tabControl.ActualWidth + InternalChildren.Count, count); + } + } + + for (var index = 0; index < count; index++) + { + if (IsTabFillEnabled) + { + itemWidth = arr[index]; + } + if (InternalChildren[index] is TabItem tabItem) + { + tabItem.RenderTransform = new TranslateTransform(); + tabItem.MaxWidth = itemWidth; + var rect = new Rect + { + X = size.Width - tabItem.BorderThickness.Left, + Width = itemWidth, + Height = TabItemHeight + }; + tabItem.Arrange(rect); + tabItem.ItemWidth = itemWidth - tabItem.BorderThickness.Left; + tabItem.CurrentIndex = index; + tabItem.TargetOffsetX = 0; + ItemDic[index] = tabItem; + size.Width += tabItem.ItemWidth; + } + } + size.Height = constraint.Height; + _oldSize = size; + return _oldSize; + } + + /// + /// 平分一个整数到一个数组中 + /// + /// + /// + /// + public static int[] DivideInt2Arr(int num, int count) + { + var arr = new int[count]; + var div = num / count; + var rest = num % count; + for (var i = 0; i < count; i++) + { + arr[i] = div; + } + for (var i = 0; i < rest; i++) + { + arr[i] += 1; + } + return arr; + } + + public TabPanel() + { + Loaded += (s, e) => + { + if (_isLoaded) return; + ForceUpdate = true; + Measure(new Size(DesiredSize.Width, ActualHeight)); + ForceUpdate = false; + foreach (var item in ItemDic.Values) + { + item.TabPanel = this; + } + _isLoaded = true; + }; + } +} diff --git a/BPASmartClient.MilkWithTea/Data/CancelRoutedEventArgs.cs b/BPASmartClient.MilkWithTea/Data/CancelRoutedEventArgs.cs new file mode 100644 index 00000000..b5a10ba4 --- /dev/null +++ b/BPASmartClient.MilkWithTea/Data/CancelRoutedEventArgs.cs @@ -0,0 +1,12 @@ +using System.Windows; + +namespace BPASmartClient.MilkWithTea.Data; + +public class CancelRoutedEventArgs : RoutedEventArgs +{ + public CancelRoutedEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) + { + } + + public bool Cancel { get; set; } +} diff --git a/BPASmartClient.MilkWithTea/Data/ValueBoxes.cs b/BPASmartClient.MilkWithTea/Data/ValueBoxes.cs new file mode 100644 index 00000000..84495fab --- /dev/null +++ b/BPASmartClient.MilkWithTea/Data/ValueBoxes.cs @@ -0,0 +1,69 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace BPASmartClient.MilkWithTea.Data; + +/// +/// 装箱后的值类型(用于提高效率) +/// +internal static class ValueBoxes +{ + internal static object TrueBox = true; + + internal static object FalseBox = false; + + internal static object VerticalBox = Orientation.Vertical; + + internal static object HorizontalBox = Orientation.Horizontal; + + internal static object VisibleBox = Visibility.Visible; + + internal static object CollapsedBox = Visibility.Collapsed; + + internal static object HiddenBox = Visibility.Hidden; + + internal static object Double01Box = .1; + + internal static object Double0Box = .0; + + internal static object Double1Box = 1.0; + + internal static object Double10Box = 10.0; + + internal static object Double20Box = 20.0; + + internal static object Double100Box = 100.0; + + internal static object Double200Box = 200.0; + + internal static object Double300Box = 300.0; + + internal static object DoubleNeg1Box = -1.0; + + internal static object Int0Box = 0; + + internal static object Int1Box = 1; + + internal static object Int2Box = 2; + + internal static object Int5Box = 5; + + internal static object Int99Box = 99; + + internal static object BooleanBox(bool value) => value ? TrueBox : FalseBox; + + internal static object OrientationBox(Orientation value) => + value == Orientation.Horizontal ? HorizontalBox : VerticalBox; + + internal static object VisibilityBox(Visibility value) + { + return value switch + { + Visibility.Visible => VisibleBox, + Visibility.Hidden => HiddenBox, + Visibility.Collapsed => CollapsedBox, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + } +} diff --git a/BPASmartClient.MilkWithTea/GLobal.cs b/BPASmartClient.MilkWithTea/GLobal.cs index 386fbb90..a3a2dfe8 100644 --- a/BPASmartClient.MilkWithTea/GLobal.cs +++ b/BPASmartClient.MilkWithTea/GLobal.cs @@ -1,5 +1,5 @@ using BPASmartClient.Model; -using Model; +using BPASmartClient.MorkMOC; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -17,46 +17,15 @@ namespace BPASmartClient.MilkWithTea public static string recipePath = string.Empty; public static string posionPath = string.Empty; - public static bool makeEnable = false; - - public static ObservableCollection MaterialRecipes { get; set; } = new ObservableCollection(); + public static bool makeEnable = true; /// - /// 设备信息列表 + /// 设备信息集合 /// public static ObservableCollection deviceConfig { get; set; } = new ObservableCollection(); - /// - /// 获取Json文件内容,转换成ObservableCollection - /// - /// - /// - /// - public static ObservableCollection GetJsonToT(string path) - { - if (!File.Exists(path)) - { - //创建该文件 - File.Create(path); - return default; - } - else - { - using (StreamReader recipeReader = new StreamReader(path))//读取json文件 - { - string datacache = ""; - string line; - while ((line = recipeReader.ReadLine()) != null) //循环将每一行数据拼接为一个完整的字符串 - { - datacache = datacache + line; - } - var res = JsonConvert.DeserializeObject>(datacache); //将string转换为class类,从而达到json文件转换的目的 - if (res != null) - return res; - else return new ObservableCollection { }; - } - } - } + + } } diff --git a/BPASmartClient.MilkWithTea/MainWindow.xaml.cs b/BPASmartClient.MilkWithTea/MainWindow.xaml.cs index 8d2e064e..9ee91d82 100644 --- a/BPASmartClient.MilkWithTea/MainWindow.xaml.cs +++ b/BPASmartClient.MilkWithTea/MainWindow.xaml.cs @@ -7,6 +7,7 @@ using BPASmartClient.IoT; using BPASmartClient.Message; using BPASmartClient.Peripheral; using BPASmartClient.ViewModel; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; @@ -36,8 +37,10 @@ namespace BPASmartClient.MilkWithTea public MainWindow() { InitializeComponent(); - - Initialize(); + + TextHelper.GetInstance.WriteTextInfo("MOC", "StartShop", "DeviceConfig"); + CreateDevice(); + //Initialize(); } @@ -45,6 +48,50 @@ namespace BPASmartClient.MilkWithTea DoubleAnimation yd1 = new DoubleAnimation();//实例化浮点动画 DoubleAnimation yd2 = new DoubleAnimation(); + private void CreateDevice() + { + DirectoryInfo directoryInfo = new DirectoryInfo(LocaPath.GetInstance().GetDeviceConfigPath); + if(!File.Exists($"{directoryInfo}Moc.json")) + { + File.WriteAllText($"{directoryInfo}Moc.json", JsonConvert.SerializeObject(GLobal.deviceConfig)); + } + string JsonString = File.ReadAllText($"{directoryInfo}MOC.json"); + var result = JsonConvert.DeserializeObject>(JsonString); + if (result != null) + { + ShopDeviceConfigViewModel.deviceConfig.Clear(); + foreach (var item in result) + { + GLobal.deviceConfig.Add(item); + } + } + + if(GLobal.deviceConfig.Count <1) + { + GLobal.deviceConfig.Add(new DeviceConfigModelJson()); + if(GLobal.deviceConfig.ElementAtOrDefault(0).deviceModels.Count <1) + { + GLobal.deviceConfig.ElementAtOrDefault(0).deviceModels.Add(new DeviceModel()); + if(GLobal.deviceConfig.ElementAtOrDefault(0).deviceModels.ElementAtOrDefault(0).communicationDevcies.Count <1) + { + GLobal.deviceConfig.ElementAtOrDefault(0).deviceModels.ElementAtOrDefault(0).communicationDevcies.Add(new CommunicationModel()); + } + } + } + if (GLobal.deviceConfig.ElementAt(0).deviceModels.ElementAt(0).communicationDevcies.ElementAt(0).variables.Count < 1) + { + for (int i = 0; i < 20; i++) + { + GLobal.deviceConfig.ElementAt(0).deviceModels.ElementAt(0).communicationDevcies.ElementAt(0).variables.Add(new Variable() + { + Id = GLobal.deviceConfig.ElementAt(0).deviceModels.ElementAt(0).communicationDevcies.ElementAt(0).variables.Count, + Address = string.Empty, + ReadLeng = 0 + }); + } + File.WriteAllText($"{LocaPath.GetInstance().GetDeviceConfigPath}MOC.json", JsonConvert.SerializeObject(GLobal.deviceConfig)); + } + } private void Initialize() { diff --git a/BPASmartClient.MilkWithTea/Model/JsonDeviceConfig.cs b/BPASmartClient.MilkWithTea/Model/JsonDeviceConfig.cs new file mode 100644 index 00000000..8d8efb1c --- /dev/null +++ b/BPASmartClient.MilkWithTea/Model/JsonDeviceConfig.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BPASmartClient.MilkWithTea.Model +{ + public class JsonDeviceConfig + { + public ObservableCollection deviceConfig { get; set; } = new ObservableCollection(); + } +} diff --git a/BPASmartClient.MilkWithTea/Model/JsonLocalRecipes.cs b/BPASmartClient.MilkWithTea/Model/JsonLocalRecipes.cs new file mode 100644 index 00000000..e5482a0e --- /dev/null +++ b/BPASmartClient.MilkWithTea/Model/JsonLocalRecipes.cs @@ -0,0 +1,23 @@ +using BPASmartClient.MorkMOC; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BPASmartClient.MilkWithTea.Model +{ + partial class JsonLocalRecipes + { + /// + /// 本地奶茶配方 + /// + public ObservableCollection localRecipes { get; set; } = new ObservableCollection(); + /// + /// 本地原料 + /// + public ObservableCollection localMaterails { get; set; } = new ObservableCollection(); + + } +} diff --git a/BPASmartClient.MilkWithTea/View/LocalConfigureView.xaml b/BPASmartClient.MilkWithTea/View/LocalConfigureView.xaml index 9b6ed672..4bcf9727 100644 --- a/BPASmartClient.MilkWithTea/View/LocalConfigureView.xaml +++ b/BPASmartClient.MilkWithTea/View/LocalConfigureView.xaml @@ -11,48 +11,6 @@ - - + + - - - - + + + + + +