diff --git a/HandyControl/Controls/Base/SelectableItem.cs b/HandyControl/Controls/Base/SelectableItem.cs index a64292d6..2556a181 100644 --- a/HandyControl/Controls/Base/SelectableItem.cs +++ b/HandyControl/Controls/Base/SelectableItem.cs @@ -29,7 +29,32 @@ namespace HandyControl.Controls if (_isMouseLeftButtonDown) { - RaiseEvent(new RoutedEventArgs(SelectedEvent, this)); + if (SelfManage) + { + if (!IsSelected) + { + IsSelected = true; + RaiseEvent(new RoutedEventArgs(SelectedEvent, this)); + } + else if (CanDeselect) + { + IsSelected = false; + RaiseEvent(new RoutedEventArgs(DeselectedEvent, this)); + } + } + else + { + if (CanDeselect) + { + RaiseEvent(IsSelected + ? new RoutedEventArgs(DeselectedEvent, this) + : new RoutedEventArgs(SelectedEvent, this)); + } + else + { + RaiseEvent(new RoutedEventArgs(SelectedEvent, this)); + } + } _isMouseLeftButtonDown = false; } } @@ -43,6 +68,24 @@ namespace HandyControl.Controls set => SetValue(IsSelectedProperty, value); } + public static readonly DependencyProperty SelfManageProperty = DependencyProperty.Register( + "SelfManage", typeof(bool), typeof(SelectableItem), new PropertyMetadata(ValueBoxes.FalseBox)); + + public bool SelfManage + { + get => (bool) GetValue(SelfManageProperty); + set => SetValue(SelfManageProperty, value); + } + + public static readonly DependencyProperty CanDeselectProperty = DependencyProperty.Register( + "CanDeselect", typeof(bool), typeof(SelectableItem), new PropertyMetadata(ValueBoxes.FalseBox)); + + public bool CanDeselect + { + get => (bool) GetValue(CanDeselectProperty); + set => SetValue(CanDeselectProperty, value); + } + public static readonly RoutedEvent SelectedEvent = EventManager.RegisterRoutedEvent("Selected", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SelectableItem)); @@ -52,5 +95,15 @@ namespace HandyControl.Controls add => AddHandler(SelectedEvent, value); remove => RemoveHandler(SelectedEvent, value); } + + public static readonly RoutedEvent DeselectedEvent = + EventManager.RegisterRoutedEvent("Deselected", RoutingStrategy.Bubble, + typeof(RoutedEventHandler), typeof(SelectableItem)); + + public event RoutedEventHandler Deselected + { + add => AddHandler(DeselectedEvent, value); + remove => RemoveHandler(DeselectedEvent, value); + } } } \ No newline at end of file diff --git a/HandyControl/Controls/Base/SimpleItemsControl.cs b/HandyControl/Controls/Base/SimpleItemsControl.cs index e466722a..0f301ffb 100644 --- a/HandyControl/Controls/Base/SimpleItemsControl.cs +++ b/HandyControl/Controls/Base/SimpleItemsControl.cs @@ -1,4 +1,5 @@ -using System.Collections.ObjectModel; +using System.Collections; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Windows; @@ -25,13 +26,23 @@ namespace HandyControl.Controls "ItemContainerStyle", typeof(Style), typeof(SimpleItemsControl), new PropertyMetadata(default(Style), OnItemContainerStyleChanged)); + public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register( + "ItemsSource", typeof(IEnumerable), typeof(SimpleItemsControl), + new PropertyMetadata(default(IEnumerable), OnItemsSourceChanged)); + public SimpleItemsControl() { var items = new ObservableCollection(); - items.CollectionChanged += OnItemsCollectionChanged; + items.CollectionChanged += OnItemsChanged; Items = items; } + public IEnumerable ItemsSource + { + get => (IEnumerable) GetValue(ItemsSourceProperty); + set => SetValue(ItemsSourceProperty, value); + } + [Bindable(true)] [Category("Content")] public Style ItemContainerStyle @@ -53,6 +64,15 @@ namespace HandyControl.Controls internal Panel ItemsHost { get; set; } + private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((SimpleItemsControl) d).OnItemsSourceChanged((IEnumerable) e.OldValue, (IEnumerable) e.NewValue); + } + + protected virtual void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) + { + } + public override void OnApplyTemplate() { ItemsHost?.Children.Clear(); @@ -63,7 +83,7 @@ namespace HandyControl.Controls Refresh(); } - private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + protected virtual void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e) { Refresh(); UpdateItems(); @@ -88,17 +108,15 @@ namespace HandyControl.Controls } } - private static void OnItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var target = d as SimpleItemsControl; - target?.Refresh(); - } + private static void OnItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + => (d as SimpleItemsControl)?.OnItemTemplateChanged(e); + + protected virtual void OnItemTemplateChanged(DependencyPropertyChangedEventArgs e) => Refresh(); private static void OnItemContainerStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var target = d as SimpleItemsControl; - target?.Refresh(); - } + => (d as SimpleItemsControl)?.OnItemContainerStyleChanged(e); + + protected virtual void OnItemContainerStyleChanged(DependencyPropertyChangedEventArgs e) => Refresh(); protected virtual void Refresh() { @@ -128,7 +146,6 @@ namespace HandyControl.Controls protected virtual void UpdateItems() { - } } } \ No newline at end of file diff --git a/HandyControl/Controls/Transfer/Transfer.cs b/HandyControl/Controls/Transfer/Transfer.cs new file mode 100644 index 00000000..196d501e --- /dev/null +++ b/HandyControl/Controls/Transfer/Transfer.cs @@ -0,0 +1,223 @@ +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using HandyControl.Interactivity; +using HandyControl.Tools.Extension; + +namespace HandyControl.Controls +{ + /// + /// 穿梭框 + /// + [TemplatePart(Name = ElementItemsOrigin, Type = typeof(SimpleItemsControl))] + [TemplatePart(Name = ElementItemsSelected, Type = typeof(SimpleItemsControl))] + public class Transfer : SimpleItemsControl + { + private const string ElementItemsOrigin = "PART_ItemsOrigin"; + + private const string ElementItemsSelected = "PART_ItemsSelected"; + + private readonly Dictionary _entryDic = new Dictionary(); + + private SimpleItemsControl _itemsOrigin; + + private SimpleItemsControl _itemsSelected; + + private IEnumerable _itemsSourceInternal; + + public static readonly RoutedEvent SelectionChangedEvent = + EventManager.RegisterRoutedEvent("SelectionChanged", RoutingStrategy.Bubble, + typeof(SelectionChangedEventHandler), typeof(Transfer)); + + public event SelectionChangedEventHandler SelectionChanged + { + add => AddHandler(SelectionChangedEvent, value); + remove => RemoveHandler(SelectionChangedEvent, value); + } + + private static readonly DependencyPropertyKey SelectedItemsPropertyKey = + DependencyProperty.RegisterReadOnly( + "SelectedItems", + typeof(IList), + typeof(Transfer), + new FrameworkPropertyMetadata(default(IList))); + + public static readonly DependencyProperty SelectedItemsProperty = SelectedItemsPropertyKey.DependencyProperty; + + public IList SelectedItems + { + get => (IList) GetValue(SelectedItemsProperty); + set => SetValue(SelectedItemsProperty, value); + } + + public Transfer() + { + CommandBindings.Add(new CommandBinding(ControlCommands.Selected, SelectItems)); + CommandBindings.Add(new CommandBinding(ControlCommands.Cancel, DeselectItems)); + + SetValue(SelectedItemsPropertyKey, new ObservableCollection()); + } + + public override void OnApplyTemplate() + { + _itemsOrigin?.ItemsHost.Children.Clear(); + _itemsSelected?.ItemsHost.Children.Clear(); + + _itemsOrigin = GetTemplateChild(ElementItemsOrigin) as SimpleItemsControl; + _itemsSelected = GetTemplateChild(ElementItemsSelected) as SimpleItemsControl; + + Refresh(); + } + + private void AddItem(object item) + { + var origin = new TransferItem + { + Content = item + }; + var selected = new TransferItem + { + Content = item, + IsOrigin = false + }; + var entry = new TransferEntry + { + OriginItem = origin, + SelectedItem = selected + }; + _entryDic.Add(item, entry); + _itemsOrigin.Items.Add(origin); + _itemsSelected.Items.Add(selected); + } + + private void RemoveItem(object item) + { + if (_entryDic.TryGetValue(item, out var entry)) + { + _itemsOrigin.Items.Remove(entry.OriginItem); + _itemsSelected.Items.Remove(entry.SelectedItem); + _entryDic.Remove(item); + } + } + + private void ClearItems() + { + _entryDic.Clear(); + _itemsOrigin.Items.Clear(); + _itemsSelected.Items.Clear(); + } + + protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) + { + if (_itemsSourceInternal != null) + { + if (_itemsSourceInternal is INotifyCollectionChanged s) + { + s.CollectionChanged -= InternalCollectionChanged; + } + + ClearItems(); + } + _itemsSourceInternal = newValue; + if (_itemsSourceInternal != null) + { + if (_itemsSourceInternal is INotifyCollectionChanged s) + { + s.CollectionChanged += InternalCollectionChanged; + } + foreach (var item in _itemsSourceInternal) + { + AddItem(item); + } + } + } + + private void InternalCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.OldItems != null) + { + foreach (var item in e.OldItems) + { + RemoveItem(item); + } + } + if (e.NewItems != null) + { + foreach (var item in e.NewItems) + { + AddItem(item); + } + } + } + + protected override DependencyObject GetContainerForItemOverride() => new TransferItem(); + + protected override bool IsItemItsOwnContainerOverride(object item) => item is TransferItem; + + private void SelectItems(object sender, ExecutedRoutedEventArgs e) + { + var list = _entryDic.Where(item => item.Value.OriginItem.IsSelected).ToList(); + list.Do(item => item.Value.StatusSwitch(true)).Do(item => SelectedItems.Add(item.Key)); + RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, new List(), list)); + } + + private void DeselectItems(object sender, ExecutedRoutedEventArgs e) + { + var list = _entryDic.Where(item => item.Value.SelectedItem.IsSelected).ToList(); + list.Do(item => item.Value.StatusSwitch(false)).Do(item => SelectedItems.Remove(item.Key)); + RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, list, new List())); + } + + protected override void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + + } + + protected override void OnItemTemplateChanged(DependencyPropertyChangedEventArgs e) + { + + } + + protected override void OnItemContainerStyleChanged(DependencyPropertyChangedEventArgs e) + { + + } + + protected override void Refresh() + { + if (_entryDic.Count > 0) + { + _entryDic.Select(item => item.Value.OriginItem).Do(item => _itemsOrigin.Items.Add(item)); + _entryDic.Select(item => item.Value.SelectedItem).Do(item => _itemsSelected.Items.Add(item)); + } + } + + private struct TransferEntry + { + public TransferItem OriginItem { get; set; } + + public TransferItem SelectedItem { get; set; } + + public void StatusSwitch(bool isTransferred) + { + if (isTransferred) + { + OriginItem.IsTransferred = true; + SelectedItem.IsTransferred = true; + OriginItem.IsSelected = false; + } + else + { + OriginItem.IsTransferred = false; + SelectedItem.IsTransferred = false; + SelectedItem.IsSelected = false; + } + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Controls/Transfer/TransferGroup.cs b/HandyControl/Controls/Transfer/TransferGroup.cs new file mode 100644 index 00000000..2ffdb8cc --- /dev/null +++ b/HandyControl/Controls/Transfer/TransferGroup.cs @@ -0,0 +1,54 @@ +using System.Collections.Specialized; +using System.Windows; + +namespace HandyControl.Controls +{ + public class TransferGroup : SimpleItemsControl + { + protected override void Refresh() + { + if (Items.Count > 0) + { + foreach (FrameworkElement item in Items) + { + item.Style = ItemContainerStyle; + ItemsHost.Children.Add(item); + } + } + } + + protected override void OnItemTemplateChanged(DependencyPropertyChangedEventArgs e) + { + + } + + protected override void OnItemContainerStyleChanged(DependencyPropertyChangedEventArgs e) + { + + } + + protected override DependencyObject GetContainerForItemOverride() => new TransferItem(); + + protected override bool IsItemItsOwnContainerOverride(object item) => item is TransferItem; + + protected override void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (ItemsHost == null) return; + if (e.OldItems != null) + { + foreach (FrameworkElement item in e.OldItems) + { + ItemsHost.Children.Remove(item); + } + } + if (e.NewItems != null) + { + foreach (FrameworkElement item in e.NewItems) + { + item.Style = ItemContainerStyle; + ItemsHost.Children.Add(item); + } + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Controls/Transfer/TransferItem.cs b/HandyControl/Controls/Transfer/TransferItem.cs new file mode 100644 index 00000000..8e798e80 --- /dev/null +++ b/HandyControl/Controls/Transfer/TransferItem.cs @@ -0,0 +1,26 @@ +using System.Windows; +using HandyControl.Data; + +namespace HandyControl.Controls +{ + public class TransferItem : SelectableItem + { + public static readonly DependencyProperty IsTransferredProperty = DependencyProperty.Register( + "IsTransferred", typeof(bool), typeof(TransferItem), new PropertyMetadata(ValueBoxes.FalseBox)); + + public bool IsTransferred + { + get => (bool) GetValue(IsTransferredProperty); + set => SetValue(IsTransferredProperty, value); + } + + public static readonly DependencyProperty IsOriginProperty = DependencyProperty.Register( + "IsOrigin", typeof(bool), typeof(TransferItem), new PropertyMetadata(ValueBoxes.TrueBox)); + + public bool IsOrigin + { + get => (bool) GetValue(IsOriginProperty); + internal set => SetValue(IsOriginProperty, value); + } + } +} \ No newline at end of file diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj index 1af9c945..5b0fc2e8 100644 --- a/HandyControl/HandyControl.csproj +++ b/HandyControl/HandyControl.csproj @@ -110,6 +110,9 @@ + + + @@ -167,6 +170,7 @@ + @@ -396,6 +400,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -700,6 +708,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/HandyControl/Themes/Styles/Base/TransferBaseStyle.xaml b/HandyControl/Themes/Styles/Base/TransferBaseStyle.xaml new file mode 100644 index 00000000..4790ad8f --- /dev/null +++ b/HandyControl/Themes/Styles/Base/TransferBaseStyle.xaml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Style.xaml b/HandyControl/Themes/Styles/Style.xaml index 52a4665c..329ca1c1 100644 --- a/HandyControl/Themes/Styles/Style.xaml +++ b/HandyControl/Themes/Styles/Style.xaml @@ -55,6 +55,7 @@ + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Transfer.xaml b/HandyControl/Themes/Styles/Transfer.xaml new file mode 100644 index 00000000..9f488dae --- /dev/null +++ b/HandyControl/Themes/Styles/Transfer.xaml @@ -0,0 +1,10 @@ + + + + + + +