mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-29 07:16:33 +08:00
fix(Selection): refresh option state after source changes
This commit is contained in:
@@ -244,6 +244,7 @@ public class Cascader : AbstractSelect
|
||||
private CascaderView? _cascaderView;
|
||||
private CompositeDisposable? _contentRightAddOnBindings;
|
||||
private bool _needSkipSyncSelectedOptions;
|
||||
private bool _isDefaultSelectOptionPathApplied;
|
||||
|
||||
static Cascader()
|
||||
{
|
||||
@@ -267,7 +268,42 @@ public class Cascader : AbstractSelect
|
||||
|
||||
private void HandleCascaderSourceChanged(AvaloniaPropertyChangedEventArgs args)
|
||||
{
|
||||
var selectedOptionPath = !IsMultiple ? BuildOptionIdentityPath(SelectedOption) : null;
|
||||
var selectedOptionPaths = IsMultiple ? BuildSelectedOptionIdentityPaths(SelectedOptions) : null;
|
||||
|
||||
_options.SetItemsSource(args.GetNewValue<IEnumerable<ICascaderOption>?>());
|
||||
|
||||
if (!IsMultiple)
|
||||
{
|
||||
if (selectedOptionPath != null &&
|
||||
TryParseSelectPath(selectedOptionPath, out var selectedOptions) &&
|
||||
selectedOptions.Count > 0)
|
||||
{
|
||||
SelectedOption = selectedOptions[^1];
|
||||
_isDefaultSelectOptionPathApplied = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyDefaultSelectOptionPath(_isDefaultSelectOptionPathApplied);
|
||||
}
|
||||
}
|
||||
else if (selectedOptionPaths != null)
|
||||
{
|
||||
var remappedOptions = new List<ICascaderOption>(selectedOptionPaths.Count);
|
||||
foreach (var path in selectedOptionPaths)
|
||||
{
|
||||
if (TryParseSelectPath(path, out var selectedOptions) &&
|
||||
selectedOptions.Count > 0)
|
||||
{
|
||||
remappedOptions.Add(selectedOptions[^1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (remappedOptions.Count > 0)
|
||||
{
|
||||
SelectedOptions = remappedOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCascaderOptionsChanged(object? sender, NotifyCollectionChangedEventArgs args)
|
||||
@@ -294,6 +330,7 @@ public class Cascader : AbstractSelect
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_isDefaultSelectOptionPathApplied = false;
|
||||
SelectedOption = null;
|
||||
SelectedOptionPath = null;
|
||||
SelectedOptions = null;
|
||||
@@ -422,8 +459,20 @@ public class Cascader : AbstractSelect
|
||||
|
||||
if (change.Property == SelectedOptionProperty)
|
||||
{
|
||||
if (SelectedOption != null)
|
||||
{
|
||||
_isDefaultSelectOptionPathApplied = false;
|
||||
}
|
||||
ConfigureSelectedOptionPath();
|
||||
}
|
||||
else if (change.Property == DefaultSelectOptionPathProperty)
|
||||
{
|
||||
_isDefaultSelectOptionPathApplied = false;
|
||||
if (SelectedOption == null)
|
||||
{
|
||||
ApplyDefaultSelectOptionPath(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
@@ -941,6 +990,82 @@ public class Cascader : AbstractSelect
|
||||
return options;
|
||||
}
|
||||
|
||||
private static List<TreeNodePath>? BuildSelectedOptionIdentityPaths(ICollection<ICascaderOption>? options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var paths = new List<TreeNodePath>(options.Count);
|
||||
foreach (var option in options)
|
||||
{
|
||||
var path = BuildOptionIdentityPath(option);
|
||||
if (path != null)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
private static TreeNodePath? BuildOptionIdentityPath(ICascaderOption? option)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var depth = CountOptionPathDepth(option);
|
||||
var segments = new string[depth];
|
||||
var current = option;
|
||||
for (var i = segments.Length - 1; current != null; i--)
|
||||
{
|
||||
var segment = current.ItemKey?.ToString() ?? current.Value?.ToString();
|
||||
if (string.IsNullOrEmpty(segment))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
segments[i] = segment;
|
||||
current = current.ParentNode as ICascaderOption;
|
||||
}
|
||||
|
||||
return new TreeNodePath(segments);
|
||||
}
|
||||
|
||||
private bool TryParseSelectPath(TreeNodePath path, out IList<ICascaderOption> pathNodes)
|
||||
{
|
||||
var segments = path.Segments;
|
||||
var isPathValid = true;
|
||||
var currentItems = BuildOptionsList(Options);
|
||||
var options = new List<ICascaderOption>(segments.Count);
|
||||
for (var i = 0; i < segments.Count; i++)
|
||||
{
|
||||
var segment = segments[i];
|
||||
var found = false;
|
||||
foreach (var currentItem in currentItems)
|
||||
{
|
||||
if (segment == currentItem.ItemKey || segment == currentItem.Value?.ToString())
|
||||
{
|
||||
options.Add(currentItem);
|
||||
currentItems = BuildOptionsList(currentItem.Children);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
isPathValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pathNodes = options;
|
||||
return isPathValid;
|
||||
}
|
||||
|
||||
private static bool AreAllChildrenSelected(
|
||||
IEnumerable<ICascaderOption> children,
|
||||
ISet<ICascaderOption> selected)
|
||||
@@ -1010,24 +1135,35 @@ public class Cascader : AbstractSelect
|
||||
return count;
|
||||
}
|
||||
|
||||
private void ApplyDefaultSelectOptionPath(bool forceRefresh = false)
|
||||
{
|
||||
if (IsMultiple || DefaultSelectOptionPath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!forceRefresh && (SelectedOption != null || SelectedOptionPath != null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryParseSelectPath(DefaultSelectOptionPath, out var options) &&
|
||||
options.Count > 0)
|
||||
{
|
||||
var parts = new string[options.Count];
|
||||
for (var i = 0; i < options.Count; i++)
|
||||
{
|
||||
parts[i] = options[i].Header?.ToString() ?? string.Empty;
|
||||
}
|
||||
SetCurrentValue(SelectedOptionPathProperty, string.Join("/", parts));
|
||||
_isDefaultSelectOptionPathApplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
if (DefaultSelectOptionPath != null && SelectedOptionPath == null)
|
||||
{
|
||||
if (_cascaderView != null)
|
||||
{
|
||||
if (_cascaderView.TryParseSelectPath(DefaultSelectOptionPath, out var options))
|
||||
{
|
||||
var parts = new string[options.Count];
|
||||
for (var i = 0; i < options.Count; i++)
|
||||
{
|
||||
parts[i] = options[i].Header?.ToString() ?? string.Empty;
|
||||
}
|
||||
SetCurrentValue(SelectedOptionPathProperty, string.Join("/", parts));
|
||||
}
|
||||
}
|
||||
}
|
||||
ApplyDefaultSelectOptionPath();
|
||||
}
|
||||
|
||||
#region 实现 FormItem 接口
|
||||
|
||||
@@ -459,7 +459,49 @@ public partial class CascaderView : TemplatedControl,
|
||||
|
||||
private void HandleCascaderSourceChanged(AvaloniaPropertyChangedEventArgs args)
|
||||
{
|
||||
ResetLevelListsForOptionsSourceChanging();
|
||||
_options.SetItemsSource(args.GetNewValue<IEnumerable<ICascaderOption>?>());
|
||||
RestoreRootLevelListItemsSource();
|
||||
if (DefaultExpandedPath != null && IsLoaded)
|
||||
{
|
||||
_defaultExpandPathApplied = false;
|
||||
ApplyDefaultExpandPath();
|
||||
_defaultExpandPathApplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetLevelListsForOptionsSourceChanging()
|
||||
{
|
||||
if (_itemsPanel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = _itemsPanel.Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_itemsPanel.Children[i] is CascaderViewLevelList levelList)
|
||||
{
|
||||
levelList.ResetVirtualizingContext();
|
||||
levelList.SelectedIndex = -1;
|
||||
levelList.SelectedItem = null;
|
||||
levelList.ItemsSource = null;
|
||||
if (i > 0)
|
||||
{
|
||||
levelList.Items.Clear();
|
||||
_itemsPanel.Children.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreRootLevelListItemsSource()
|
||||
{
|
||||
if (_rootLevelList == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rootLevelList.ItemsSource = Options;
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
|
||||
@@ -78,18 +78,16 @@ internal class CascaderViewLevelList : SelectingItemsControl, IListVirtualizingC
|
||||
if (this is IListVirtualizingContextAware listVirtualizingContextAwareControl &&
|
||||
cascaderViewItem is IListItemVirtualizingContextAware virtualListItem)
|
||||
{
|
||||
if (item is ICascaderOption option)
|
||||
{
|
||||
NotifyRestoreDefaultContext(cascaderViewItem, option);
|
||||
}
|
||||
|
||||
if (_virtualRestoreContext.TryGetValue(index, out var context))
|
||||
{
|
||||
listVirtualizingContextAwareControl.RestoreVirtualizingContext(cascaderViewItem, context);
|
||||
_virtualRestoreContext.Remove(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item is ICascaderOption option)
|
||||
{
|
||||
NotifyRestoreDefaultContext(cascaderViewItem, option);
|
||||
}
|
||||
}
|
||||
virtualListItem.VirtualIndex = index;
|
||||
}
|
||||
else
|
||||
@@ -245,6 +243,7 @@ internal class CascaderViewLevelList : SelectingItemsControl, IListVirtualizingC
|
||||
{
|
||||
item.SetCurrentValue(CascaderViewItem.HeaderProperty, option);
|
||||
item.ItemKey = option.ItemKey;
|
||||
item.SetCurrentValue(CascaderViewItem.ValueProperty, option.Value);
|
||||
item.SetCurrentValue(CascaderViewItem.IconProperty, option.Icon);
|
||||
item.SetCurrentValue(CascaderViewItem.IsCheckedProperty, option.IsChecked);
|
||||
item.SetCurrentValue(CascaderViewItem.IsEnabledProperty, option.IsEnabled);
|
||||
@@ -311,6 +310,10 @@ internal class CascaderViewLevelList : SelectingItemsControl, IListVirtualizingC
|
||||
|
||||
protected virtual void NotifyClearContainerForVirtualizingContext(CascaderViewItem item)
|
||||
{
|
||||
item.ClearValue(CascaderViewItem.HeaderProperty);
|
||||
item.ClearValue(CascaderViewItem.ValueProperty);
|
||||
item.ItemKey = null;
|
||||
item.ClearValue(CascaderViewItem.IconProperty);
|
||||
item.ClearValue(CascaderViewItem.IsEnabledProperty);
|
||||
item.ClearValue(CascaderViewItem.IsCheckedProperty);
|
||||
item.ClearValue(CascaderViewItem.IsExpandedProperty);
|
||||
@@ -318,6 +321,11 @@ internal class CascaderViewLevelList : SelectingItemsControl, IListVirtualizingC
|
||||
item.AsyncLoaded = false;
|
||||
}
|
||||
|
||||
internal void ResetVirtualizingContext()
|
||||
{
|
||||
_virtualRestoreContext.Clear();
|
||||
}
|
||||
|
||||
void IListVirtualizingContextAware.SaveVirtualizingContext(Control item, IDictionary<object, object?> context)
|
||||
{
|
||||
if (item is CascaderViewItem cascaderViewItem)
|
||||
|
||||
@@ -352,6 +352,10 @@ public class ListBox : AvaloniaListBox,
|
||||
{
|
||||
listBoxItem[!ListBoxItem.ContentTemplateProperty] = this[!ItemTemplateProperty];
|
||||
}
|
||||
else
|
||||
{
|
||||
listBoxItem.ClearValue(ListBoxItem.ContentTemplateProperty);
|
||||
}
|
||||
listBoxItem[!ListBoxItem.SizeTypeProperty] = this[!SizeTypeProperty];
|
||||
listBoxItem[!ListBoxItem.IsMotionEnabledProperty] = this[!IsMotionEnabledProperty];
|
||||
listBoxItem[!ListBoxItem.SelectedIndicatorProperty] = this[!SelectedIndicatorProperty];
|
||||
@@ -626,14 +630,13 @@ public class ListBox : AvaloniaListBox,
|
||||
|
||||
protected virtual void NotifyRestoreDefaultContext(ListBoxItem item, IListItemData itemData)
|
||||
{
|
||||
if (!item.IsSet(ListBoxItem.ContentProperty))
|
||||
{
|
||||
item.SetCurrentValue(ListBoxItem.ContentProperty, itemData);
|
||||
}
|
||||
item.SetCurrentValue(ListBoxItem.ContentProperty, itemData);
|
||||
}
|
||||
|
||||
protected virtual void NotifyClearContainerForVirtualizingContext(ListBoxItem item)
|
||||
{
|
||||
item.ClearValue(ListBoxItem.ContentProperty);
|
||||
item.ClearValue(ListBoxItem.ContentTemplateProperty);
|
||||
item.ClearValue(ListBoxItem.IsEnabledProperty);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,10 +43,7 @@ public partial class ListView : IListVirtualizingContextAware
|
||||
|
||||
protected virtual void NotifyRestoreDefaultContext(ListViewItem item, IListItemData itemData)
|
||||
{
|
||||
if (!item.IsSet(ListViewItem.ContentProperty))
|
||||
{
|
||||
item.SetCurrentValue(ListViewItem.ContentProperty, itemData);
|
||||
}
|
||||
item.SetCurrentValue(ListViewItem.ContentProperty, itemData);
|
||||
if (itemData is IGroupListItemData groupListItemData)
|
||||
{
|
||||
item.SetCurrentValue(ListViewItem.IsGroupItemProperty, groupListItemData.IsGroupItem);
|
||||
@@ -56,6 +53,8 @@ public partial class ListView : IListVirtualizingContextAware
|
||||
|
||||
protected virtual void NotifyClearContainerForVirtualizingContext(ListViewItem item)
|
||||
{
|
||||
item.ClearValue(ListViewItem.ContentProperty);
|
||||
item.ClearValue(ListViewItem.ContentTemplateProperty);
|
||||
item.ClearValue(ListViewItem.IsEnabledProperty);
|
||||
item.ClearValue(ListViewItem.IsGroupItemProperty);
|
||||
}
|
||||
|
||||
@@ -649,6 +649,14 @@ public partial class ListView : ItemsControl, ISizeTypeAware, IMotionAwareContro
|
||||
{
|
||||
listItem[!ListViewItem.ContentTemplateProperty] = this[!GroupItemTemplateProperty];
|
||||
}
|
||||
else if (ItemTemplate != null)
|
||||
{
|
||||
listItem[!ListViewItem.ContentTemplateProperty] = this[!ItemTemplateProperty];
|
||||
}
|
||||
else
|
||||
{
|
||||
listItem.ClearValue(ListViewItem.ContentTemplateProperty);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -656,6 +664,10 @@ public partial class ListView : ItemsControl, ISizeTypeAware, IMotionAwareContro
|
||||
{
|
||||
listItem[!ListViewItem.ContentTemplateProperty] = this[!ItemTemplateProperty];
|
||||
}
|
||||
else
|
||||
{
|
||||
listItem.ClearValue(ListViewItem.ContentTemplateProperty);
|
||||
}
|
||||
}
|
||||
|
||||
listItem[!ListViewItem.SizeTypeProperty] = this[!SizeTypeProperty];
|
||||
|
||||
@@ -989,12 +989,96 @@ public partial class Select : AbstractSelect
|
||||
|
||||
private void HandleOptionsSourcePropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
ClearValue();
|
||||
var selectedOptionIdentity = Mode == SelectMode.Single ? BuildOptionIdentity(SelectedOption) : null;
|
||||
var selectedOptionIdentities = Mode != SelectMode.Single ? BuildSelectedOptionIdentities(SelectedOptions) : null;
|
||||
|
||||
if (!Options.IsReadOnly)
|
||||
{
|
||||
Options.Clear();
|
||||
}
|
||||
Options.SetItemsSource(change.GetNewValue<IEnumerable<ISelectOption>?>());
|
||||
|
||||
if (Mode == SelectMode.Single)
|
||||
{
|
||||
if (selectedOptionIdentity != null &&
|
||||
TryFindOptionByIdentity(selectedOptionIdentity, out var remappedOption))
|
||||
{
|
||||
SelectedOption = remappedOption;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedOption = null;
|
||||
ConfigureDefaultValues();
|
||||
}
|
||||
}
|
||||
else if (selectedOptionIdentities != null)
|
||||
{
|
||||
var remappedOptions = new List<ISelectOption>(selectedOptionIdentities.Count);
|
||||
foreach (var identity in selectedOptionIdentities)
|
||||
{
|
||||
if (TryFindOptionByIdentity(identity, out var remappedOption))
|
||||
{
|
||||
remappedOptions.Add(remappedOption);
|
||||
}
|
||||
}
|
||||
|
||||
SelectedOptions = remappedOptions.Count > 0 ? remappedOptions : null;
|
||||
ConfigureDefaultValues();
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfigureDefaultValues();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string>? BuildSelectedOptionIdentities(ICollection<ISelectOption>? options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var identities = new List<string>(options.Count);
|
||||
foreach (var option in options)
|
||||
{
|
||||
var identity = BuildOptionIdentity(option);
|
||||
if (identity != null)
|
||||
{
|
||||
identities.Add(identity);
|
||||
}
|
||||
}
|
||||
return identities;
|
||||
}
|
||||
|
||||
private static string? BuildOptionIdentity(ISelectOption? option)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var itemKey = option.ItemKey?.ToString();
|
||||
if (!string.IsNullOrEmpty(itemKey))
|
||||
{
|
||||
return itemKey;
|
||||
}
|
||||
return option.Content?.ToString();
|
||||
}
|
||||
|
||||
private bool TryFindOptionByIdentity(string identity, out ISelectOption option)
|
||||
{
|
||||
foreach (var item in Options)
|
||||
{
|
||||
if (item is ISelectOption currentOption &&
|
||||
identity == BuildOptionIdentity(currentOption))
|
||||
{
|
||||
option = currentOption;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
option = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ConfigureDefaultValues()
|
||||
|
||||
@@ -356,7 +356,38 @@ public class TreeSelect : AbstractSelect
|
||||
|
||||
private void HandleItemsSourceChanged(AvaloniaPropertyChangedEventArgs args)
|
||||
{
|
||||
var selectedItemPath = !IsMultiple ? BuildNodeIdentityPath(SelectedItem) : null;
|
||||
var selectedItemPaths = IsMultiple ? BuildSelectedNodeIdentityPaths(SelectedItems) : null;
|
||||
|
||||
_items.SetItemsSource(args.GetNewValue<IEnumerable<ITreeItemNode>?>());
|
||||
|
||||
if (!IsMultiple)
|
||||
{
|
||||
if (selectedItemPath != null &&
|
||||
TryParseNodePath(selectedItemPath, out var selectedItems) &&
|
||||
selectedItems.Count > 0)
|
||||
{
|
||||
SelectedItem = selectedItems[^1];
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedItem = null;
|
||||
}
|
||||
}
|
||||
else if (selectedItemPaths != null)
|
||||
{
|
||||
var remappedItems = new List<ITreeItemNode>(selectedItemPaths.Count);
|
||||
foreach (var path in selectedItemPaths)
|
||||
{
|
||||
if (TryParseNodePath(path, out var selectedItems) &&
|
||||
selectedItems.Count > 0)
|
||||
{
|
||||
remappedItems.Add(selectedItems[^1]);
|
||||
}
|
||||
}
|
||||
|
||||
SelectedItems = remappedItems.Count > 0 ? remappedItems : null;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleItemsChanged(object? sender, NotifyCollectionChangedEventArgs args)
|
||||
@@ -1153,6 +1184,95 @@ public class TreeSelect : AbstractSelect
|
||||
return items;
|
||||
}
|
||||
|
||||
private static List<TreeNodePath>? BuildSelectedNodeIdentityPaths(ICollection<ITreeItemNode>? nodes)
|
||||
{
|
||||
if (nodes == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var paths = new List<TreeNodePath>(nodes.Count);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var path = BuildNodeIdentityPath(node);
|
||||
if (path != null)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
private static TreeNodePath? BuildNodeIdentityPath(ITreeItemNode? node)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var depth = CountNodePathDepth(node);
|
||||
var segments = new string[depth];
|
||||
var current = node;
|
||||
for (var i = segments.Length - 1; current != null; i--)
|
||||
{
|
||||
var segment = current.ItemKey?.ToString() ?? current.Value?.ToString();
|
||||
if (string.IsNullOrEmpty(segment))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
segments[i] = segment;
|
||||
current = current.ParentNode as ITreeItemNode;
|
||||
}
|
||||
|
||||
return new TreeNodePath(segments);
|
||||
}
|
||||
|
||||
private bool TryParseNodePath(TreeNodePath path, out IList<ITreeItemNode> pathNodes)
|
||||
{
|
||||
var segments = path.Segments;
|
||||
var isPathValid = true;
|
||||
var currentItems = BuildTreeNodeList(Items);
|
||||
var nodes = new List<ITreeItemNode>(segments.Count);
|
||||
for (var i = 0; i < segments.Count; i++)
|
||||
{
|
||||
var segment = segments[i];
|
||||
var found = false;
|
||||
foreach (var currentItem in currentItems)
|
||||
{
|
||||
if (segment == currentItem.ItemKey || segment == currentItem.Value?.ToString())
|
||||
{
|
||||
nodes.Add(currentItem);
|
||||
currentItems = BuildTreeNodeList(currentItem.Children);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
isPathValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pathNodes = nodes;
|
||||
return isPathValid;
|
||||
}
|
||||
|
||||
private static int CountNodePathDepth(ITreeItemNode node)
|
||||
{
|
||||
var count = 0;
|
||||
var current = node;
|
||||
while (current != null)
|
||||
{
|
||||
count++;
|
||||
current = current.ParentNode as ITreeItemNode;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static bool AreAllChildrenSelected(
|
||||
IEnumerable<ITreeItemNode> children,
|
||||
ISet<ITreeItemNode> selected)
|
||||
|
||||
@@ -1090,7 +1090,7 @@ public partial class TreeView : AvaloniaTreeView,
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (treeViewItem.ItemKey != null && treeViewItem.ItemKey.Value == segment)
|
||||
if (IsPathSegmentMatched(treeViewItem, segment))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsExpandedProperty, true);
|
||||
if (treeViewItem.Presenter?.Panel == null)
|
||||
@@ -1148,7 +1148,7 @@ public partial class TreeView : AvaloniaTreeView,
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (treeViewItem.ItemKey != null && treeViewItem.ItemKey.Value == segment)
|
||||
if (IsPathSegmentMatched(treeViewItem, segment))
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsExpandedProperty, true);
|
||||
if (treeViewItem.Presenter?.Panel == null)
|
||||
@@ -1217,7 +1217,7 @@ public partial class TreeView : AvaloniaTreeView,
|
||||
return null;
|
||||
}
|
||||
|
||||
if (treeViewItem.ItemKey != null && treeViewItem.ItemKey.Value == segment)
|
||||
if (IsPathSegmentMatched(treeViewItem, segment))
|
||||
{
|
||||
pathNodeExpandStatus.Add(treeViewItem.IsExpanded);
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsExpandedProperty, true);
|
||||
@@ -1262,6 +1262,15 @@ public partial class TreeView : AvaloniaTreeView,
|
||||
SetCurrentValue(IsMotionEnabledProperty, originIsMotionEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPathSegmentMatched(TreeViewItem treeViewItem, string segment)
|
||||
{
|
||||
if (treeViewItem.ItemKey != null && treeViewItem.ItemKey.Value == segment)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return treeViewItem.Value?.ToString() == segment;
|
||||
}
|
||||
|
||||
private (ISet<object>, ISet<object>) SetupParentNodeCheckedStatus(TreeViewItem viewItem)
|
||||
{
|
||||
@@ -1314,6 +1323,147 @@ public partial class TreeView : AvaloniaTreeView,
|
||||
return (checkedParents, unCheckedParents);
|
||||
}
|
||||
|
||||
private void ConfigureStateAfterItemsSourceChanged()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedItemPath = BuildNodeIdentityPath(SelectedItem as ITreeItemNode);
|
||||
var selectedItemPaths = BuildNodeIdentityPaths(SelectedItems);
|
||||
var checkedItemPaths = BuildNodeIdentityPaths(CheckedItems);
|
||||
|
||||
SetCurrentValue(SelectedItemProperty, null);
|
||||
SelectedItems.Clear();
|
||||
CheckedItems.Clear();
|
||||
|
||||
var selectionRestored = false;
|
||||
if (selectedItemPath != null)
|
||||
{
|
||||
selectionRestored = TrySelectNodePath(selectedItemPath);
|
||||
}
|
||||
if (!selectionRestored && selectedItemPaths != null)
|
||||
{
|
||||
foreach (var path in selectedItemPaths)
|
||||
{
|
||||
selectionRestored |= TrySelectNodePath(path);
|
||||
}
|
||||
}
|
||||
if (!selectionRestored)
|
||||
{
|
||||
ConfigureDefaultSelectedPaths();
|
||||
}
|
||||
|
||||
var checkedRestored = false;
|
||||
if (checkedItemPaths != null)
|
||||
{
|
||||
foreach (var path in checkedItemPaths)
|
||||
{
|
||||
checkedRestored |= TryCheckNodePath(path);
|
||||
}
|
||||
}
|
||||
if (!checkedRestored)
|
||||
{
|
||||
ConfigureDefaultCheckedPaths();
|
||||
}
|
||||
|
||||
if (IsDefaultExpandAll)
|
||||
{
|
||||
ExpandAll(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfigureDefaultExpandedPaths();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<TreeNodePath>? BuildNodeIdentityPaths(IList? nodes)
|
||||
{
|
||||
if (nodes == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var paths = new List<TreeNodePath>(nodes.Count);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (node is ITreeItemNode treeItemNode)
|
||||
{
|
||||
var path = BuildNodeIdentityPath(treeItemNode);
|
||||
if (path != null)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
private static TreeNodePath? BuildNodeIdentityPath(ITreeItemNode? node)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var depth = CountTreeItemPathDepth(node);
|
||||
var segments = new string[depth];
|
||||
var current = node;
|
||||
for (var i = segments.Length - 1; current != null; i--)
|
||||
{
|
||||
var segment = current.ItemKey?.ToString() ?? current.Value?.ToString();
|
||||
if (string.IsNullOrEmpty(segment))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
segments[i] = segment;
|
||||
current = current.ParentNode as ITreeItemNode;
|
||||
}
|
||||
|
||||
return new TreeNodePath(segments);
|
||||
}
|
||||
|
||||
private bool TrySelectNodePath(TreeNodePath path)
|
||||
{
|
||||
var selected = false;
|
||||
TraverTreeViewPath(path, (treeViewItem, i) =>
|
||||
{
|
||||
if (i == path.Length - 1)
|
||||
{
|
||||
var item = TreeItemFromContainer(treeViewItem);
|
||||
if (item != null)
|
||||
{
|
||||
if (!SelectedItems.Contains(item))
|
||||
{
|
||||
SelectedItems.Add(item);
|
||||
}
|
||||
if (SelectionMode == SelectionMode.Single)
|
||||
{
|
||||
SetCurrentValue(SelectedItemProperty, item);
|
||||
}
|
||||
selected = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
return selected;
|
||||
}
|
||||
|
||||
private bool TryCheckNodePath(TreeNodePath path)
|
||||
{
|
||||
var isChecked = false;
|
||||
TraverTreeViewPath(path, (treeViewItem, i) =>
|
||||
{
|
||||
if (i == path.Length - 1)
|
||||
{
|
||||
treeViewItem.SetCurrentValue(TreeViewItem.IsCheckedProperty, true);
|
||||
isChecked = true;
|
||||
}
|
||||
});
|
||||
return isChecked;
|
||||
}
|
||||
|
||||
private void GetChildCheckStatus(TreeViewItem parentTreeItem, out bool isAllChecked, out bool isAnyChecked)
|
||||
{
|
||||
isAllChecked = false;
|
||||
@@ -1490,6 +1640,11 @@ public partial class TreeView : AvaloniaTreeView,
|
||||
SelectedItems.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (change.Property == ItemsSourceProperty)
|
||||
{
|
||||
Dispatcher.Post(ConfigureStateAfterItemsSourceChanged);
|
||||
}
|
||||
}
|
||||
|
||||
protected void HandleSwitcherRotationIconChanged()
|
||||
|
||||
@@ -847,6 +847,7 @@ public class TreeViewItem : AvaloniaTreeItem, IRadioButton, ITreeItemNode
|
||||
treeViewItem.SetCurrentValue(IsEnabledProperty, treeItemData.IsEnabled);
|
||||
treeViewItem.SetCurrentValue(IsExpandedProperty, treeItemData.IsExpanded);
|
||||
treeViewItem.SetCurrentValue(IsIndicatorEnabledProperty, treeItemData.IsIndicatorEnabled);
|
||||
treeViewItem.SetCurrentValue(ValueProperty, treeItemData.Value);
|
||||
|
||||
treeViewItem.ItemKey = treeItemData.ItemKey;
|
||||
treeViewItem.IsLeaf = treeItemData.IsLeaf;
|
||||
|
||||
Reference in New Issue
Block a user