Skip to content

Commit 13a3ab8

Browse files
committed
Improve text selection
1 parent ddf9ea9 commit 13a3ab8

1 file changed

Lines changed: 62 additions & 17 deletions

File tree

src/MahApps.Metro/Controls/MultiSelectionComboBox/MultiSelectionComboBox.cs

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public MultiSelectionComboBox()
6363
private ListBox? PART_SelectedItemsPresenter;
6464

6565
private bool isUserdefinedTextInputPending;
66+
private bool isTextChanging; // This flag indicates if the text is changed by us, so we don't want to refire the TextChangedEvent.
6667
private bool shouldDoTextReset; // Defines if the Text should be reset after selecting items from string
6768
private bool shouldAddItems; // Defines if the MSCB should add new items from text input. Don't set this to true while input is pending. We cannot know how long the user needs for typing.
6869
private bool IsSyncingSelectedItems; // true if syncing in one or the other direction already running
@@ -553,14 +554,17 @@ public void ResetEditableText()
553554
/// <summary>
554555
/// Updates the Text of the editable TextBox.
555556
/// Sets the custom Text if any otherwise the concatenated string.
556-
/// </summary>
557+
/// </summary>
558+
557559
private void UpdateEditableText(bool forceUpdate = false)
558560
{
559561
if (this.PART_EditableTextBox is null || (this.PART_EditableTextBox.IsKeyboardFocused && !forceUpdate))
560562
{
561563
return;
562564
}
563565

566+
this.isTextChanging = true;
567+
564568
var oldSelectionStart = this.PART_EditableTextBox.SelectionStart;
565569
var oldSelectionLength = this.PART_EditableTextBox.SelectionLength;
566570
var oldTextLength = this.PART_EditableTextBox.Text.Length;
@@ -588,6 +592,8 @@ private void UpdateEditableText(bool forceUpdate = false)
588592
this.PART_EditableTextBox.SelectionStart = oldSelectionStart;
589593
this.PART_EditableTextBox.SelectionLength = oldSelectionLength;
590594
}
595+
596+
this.isTextChanging = false;
591597
}
592598

593599
private void UpdateDisplaySelectedItems()
@@ -654,7 +660,7 @@ private void UpdateDisplaySelectedItems(SelectedItemsOrderType selectedItemsOrde
654660

655661
private void SelectItemsFromText(int millisecondsToWait)
656662
{
657-
if (!this.isUserdefinedTextInputPending)
663+
if (!this.isUserdefinedTextInputPending || this.isTextChanging)
658664
{
659665
return;
660666
}
@@ -746,10 +752,16 @@ private void UpdateSelectedItemsFromTextTimer_Tick(object sender, EventArgs e)
746752

747753
case SelectionMode.Multiple:
748754
case SelectionMode.Extended:
755+
if (this.SelectedItems is null)
756+
{
757+
break; // Exit here if we have no SelectedItems yet
758+
}
759+
749760
var strings = !string.IsNullOrEmpty(this.Separator) ? this.Text?.Split(new[] { this.Separator }, StringSplitOptions.RemoveEmptyEntries) : null;
750761

751-
// clear items here, so we can use the text before.
752-
this.SelectedItems?.Clear();
762+
int position = 0;
763+
764+
// this.SelectedItems?.Clear();
753765

754766
if (strings is not null)
755767
{
@@ -762,8 +774,27 @@ private void UpdateSelectedItemsFromTextTimer_Tick(object sender, EventArgs e)
762774
{
763775
if (this.ObjectToStringComparer.CheckIfStringMatchesObject(stringObject, item, this.EditableTextStringComparision, this.SelectedItemStringFormat))
764776
{
765-
this.SelectedItems?.Add(item);
766-
foundItem = true;
777+
var oldPosition = this.SelectedItems.IndexOf(item);
778+
779+
if (oldPosition < 0) // if old index is <0 the item is not in list yet. let's add it.
780+
{
781+
this.SelectedItems.Insert(position,item);
782+
foundItem = true;
783+
position++;
784+
}
785+
else if (oldPosition > position) // if the item has a wrong position in list we need to swap it accordingly.
786+
{
787+
this.SelectedItems.RemoveAt(oldPosition);
788+
this.SelectedItems.Insert(position, item);
789+
790+
foundItem = true;
791+
position++;
792+
}
793+
else if (oldPosition == position)
794+
{
795+
foundItem = true;
796+
position++;
797+
}
767798
}
768799
}
769800
}
@@ -772,7 +803,8 @@ private void UpdateSelectedItemsFromTextTimer_Tick(object sender, EventArgs e)
772803
{
773804
if (this.shouldAddItems && this.TryAddObjectFromString(stringObject, out var result))
774805
{
775-
this.SelectedItems?.Add(result);
806+
this.SelectedItems.Insert(position,result);
807+
position++;
776808
}
777809
else
778810
{
@@ -782,6 +814,12 @@ private void UpdateSelectedItemsFromTextTimer_Tick(object sender, EventArgs e)
782814
}
783815
}
784816

817+
// Remove Items if needed.
818+
while (this.SelectedItems.Count > position)
819+
{
820+
this.SelectedItems.RemoveAt(position);
821+
}
822+
785823
break;
786824

787825
default:
@@ -865,9 +903,9 @@ private bool TryAddObjectFromString(string? input, out object? result)
865903
}
866904
}
867905

868-
#endregion
906+
#endregion
869907

870-
#region Commands
908+
#region Commands
871909

872910
// Clear Text Command
873911
public static RoutedUICommand ClearContentCommand { get; } = new RoutedUICommand("ClearContent", nameof(ClearContentCommand), typeof(MultiSelectionComboBox));
@@ -935,9 +973,9 @@ private static void RemoveItemCommand_CanExecute(object sender, CanExecuteRouted
935973
}
936974
}
937975

938-
#endregion
976+
#endregion
939977

940-
#region Overrides
978+
#region Overrides
941979

942980
public override void OnApplyTemplate()
943981
{
@@ -966,6 +1004,8 @@ public override void OnApplyTemplate()
9661004
{
9671005
selectedItemsCollection.CollectionChanged -= this.PART_PopupListBox_SelectedItems_CollectionChanged;
9681006
selectedItemsCollection.CollectionChanged += this.PART_PopupListBox_SelectedItems_CollectionChanged;
1007+
1008+
PART_PopupListBox.Unloaded += (s, e) => { selectedItemsCollection.CollectionChanged -= this.PART_PopupListBox_SelectedItems_CollectionChanged; };
9691009
}
9701010

9711011
// Do update the text
@@ -985,8 +1025,8 @@ private void PART_PopupListBox_SelectedItems_CollectionChanged(object sender, No
9851025
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
9861026
{
9871027
base.OnSelectionChanged(e);
988-
this.UpdateEditableText();
9891028
this.UpdateDisplaySelectedItems();
1029+
this.UpdateEditableText();
9901030
}
9911031

9921032
private void MultiSelectionComboBox_Loaded(object sender, EventArgs e)
@@ -1112,7 +1152,7 @@ private void MoveFocusToDropDown()
11121152
}
11131153

11141154
var index = this.PART_PopupListBox.SelectedIndex;
1115-
if (index < 0)
1155+
if (index < 0 && this.PART_PopupListBox.Items.Count > 0)
11161156
{
11171157
index = 0;
11181158
}
@@ -1527,16 +1567,21 @@ private bool IsSelectableHelper(object o)
15271567
return (bool)d.GetValue(IsEnabledProperty);
15281568
}
15291569

1530-
#endregion
1570+
#endregion
15311571

1532-
#region Events
1572+
#region Events
15331573

15341574
#if NET5_0_OR_GREATER
15351575
private void SelectedItemsImpl_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
15361576
#else
15371577
private void SelectedItemsImpl_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
15381578
#endif
15391579
{
1580+
if (this.PART_PopupListBox is null)
1581+
{
1582+
this.ApplyTemplate();
1583+
}
1584+
15401585
this.SyncSelectedItems(sender as IList, this.PART_PopupListBox?.SelectedItems, e);
15411586
}
15421587

@@ -1672,7 +1717,7 @@ private static void UpdateText(DependencyObject d, DependencyPropertyChangedEven
16721717

16731718
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
16741719
{
1675-
if (d is MultiSelectionComboBox multiSelectionComboBox)
1720+
if (d is MultiSelectionComboBox multiSelectionComboBox && !multiSelectionComboBox.isTextChanging)
16761721
{
16771722
multiSelectionComboBox.UpdateHasCustomText(null);
16781723
multiSelectionComboBox.isUserdefinedTextInputPending = true;
@@ -1720,6 +1765,6 @@ public event AddedItemEventArgsHandler AddedItem
17201765
remove => this.RemoveHandler(AddedItemEvent, value);
17211766
}
17221767

1723-
#endregion
1768+
#endregion
17241769
}
17251770
}

0 commit comments

Comments
 (0)